/*
* 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) 2014, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.beans;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import commons.Exceptions;
import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.delegate.DelegateExecution;
import org.apache.commons.lang.StringUtils;
import org.glassfish.osgicdi.OSGiService;
import ru.codeinside.adm.AdminService;
import ru.codeinside.adm.AdminServiceProvider;
import ru.codeinside.adm.database.Bid;
import ru.codeinside.adm.database.ClientRequestEntity;
import ru.codeinside.adm.database.ExternalGlue;
import ru.codeinside.adm.database.InfoSystemService;
import ru.codeinside.adm.database.ServiceResponseEntity;
import ru.codeinside.gses.API;
import ru.codeinside.gses.activiti.Activiti;
import ru.codeinside.gses.activiti.ReceiptEnsurance;
import ru.codeinside.gses.webui.form.FormOvSignatureSeq;
import ru.codeinside.gses.webui.form.ProtocolUtils;
import ru.codeinside.gses.webui.gws.ClientRefRegistry;
import ru.codeinside.gses.webui.gws.ServiceRefRegistry;
import ru.codeinside.gses.webui.gws.TRef;
import ru.codeinside.gses.webui.osgi.LogCustomizer;
import ru.codeinside.gws.api.AppData;
import ru.codeinside.gws.api.Client;
import ru.codeinside.gws.api.ClientFailureAware;
import ru.codeinside.gws.api.ClientLog;
import ru.codeinside.gws.api.ClientProtocol;
import ru.codeinside.gws.api.ClientRequest;
import ru.codeinside.gws.api.ClientResponse;
import ru.codeinside.gws.api.CryptoProvider;
import ru.codeinside.gws.api.Enclosure;
import ru.codeinside.gws.api.ExchangeContext;
import ru.codeinside.gws.api.Packet;
import ru.codeinside.gws.api.ProtocolFactory;
import ru.codeinside.gws.api.Revision;
import ru.codeinside.gws.api.Server;
import ru.codeinside.gws.api.ServerRejectAware;
import ru.codeinside.gws.api.ServerResponse;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.xml.namespace.QName;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.apache.commons.lang.StringUtils.defaultString;
@Named("smev")
@Singleton
public class Smev implements ReceiptEnsurance {
public static final String SERVER_RESPONSE_ID = "ServerResponseEntityId";
final static String CLIENT_BPMN_ERROR = "client_bpmn_error"; // ошибки до отпрвки в смэв
final static String SERVER_BPMN_ERROR = "server_bpmn_error"; // ошибки при отправке в смэв
final static String SUDDENLY_BPMN_ERROR = "suddenly_bpmn_error"; //не предполагаемые ошибки(например ошибки разработчиков сервисов)
final static Logger logger = Logger.getLogger(Smev.class.getName());
@Inject
ClientRefRegistry registry;
@Inject
AdminService adminService;
@Inject
ServiceRefRegistry serviceRegistry;
@Inject
@OSGiService(dynamic = true)
CryptoProvider cryptoProvider;
@Inject
@OSGiService(dynamic = true)
ProtocolFactory protocolFactory;
// TODO не передавать DelegateExecution как параметер
public void call(DelegateExecution execution, String serviceName) {
innerCall(execution, serviceName, false);
}
private void innerCall(DelegateExecution execution, String serviceName, boolean wrapErrors) {
ExchangeContext context = new ActivitiExchangeContext(execution);
InfoSystemService service = validateAndGetService(serviceName);
Client client = findByNameAndVersion(serviceName, service.getSversion());
ClientRequest clientRequest;
//serviceName нужен, чтобы в случае параллельного выполнения отличались имена переменных в разных потоках
Long requestId = (Long) context.getVariable(serviceName + FormOvSignatureSeq.REQUEST_ID);
boolean isDataFlow = (requestId != null);
if (isDataFlow && !ProtocolUtils.isPing(context)) {
ClientRequestEntity entity = AdminServiceProvider.get().getClientRequestEntity(requestId);
clientRequest = createClientRequest(entity, context, execution.getId(), "");//TODO VariableName?
} else {
ProtocolUtils.writeInfoSystemsToContext(service, context);
clientRequest = client.createClientRequest(context);
}
callGws(execution.getProcessInstanceId(), serviceName, client, context, clientRequest, service, wrapErrors);
}
//минимальный список ошибок
//org.glassfish.osgicdi.ServiceUnavailableException
//BpmnError
//UnsupportedOperationException
//IllegalStateException
//IllegalArgumentException
/*
Возможные варианты кодов bmpnerror
client_bpmn_error - ошибки до отправки в смэв
server_bpmn_error - ошибки при отправке в смэв
suddenly_bpmn_error - не предполагаемые ошибки(например ошибки разработчиков сервисов)
* - ожидаемые bpmn ошибки
*/
public void managedCall(DelegateExecution execution, String serviceName) {
try {
innerCall(execution, serviceName, true);
} catch (BpmnError e) {
registerError(execution, serviceName, e, "managedCall");
throw e;
} catch (org.glassfish.osgicdi.ServiceUnavailableException e) {
registerError(execution, serviceName, e, "managedCall");
throw new BpmnError(CLIENT_BPMN_ERROR);
} catch (UnsupportedOperationException e) {
registerError(execution, serviceName, e, "managedCall");
throw new BpmnError(CLIENT_BPMN_ERROR);
} catch (IllegalStateException e) {
registerError(execution, serviceName, e, "managedCall");
throw new BpmnError(CLIENT_BPMN_ERROR);
} catch (IllegalArgumentException e) {
registerError(execution, serviceName, e, "managedCall");
throw new BpmnError(CLIENT_BPMN_ERROR);
} catch (Throwable th) {
registerError(execution, serviceName, th, "managedCall");
throw new BpmnError(SUDDENLY_BPMN_ERROR);
}
}
@SuppressWarnings("unused") // Do NOT remove (used in BPMN API)
public void infoCall(DelegateExecution execution, String serviceName) {
try {
managedCall(execution, serviceName);
} catch (BpmnError bpmnError) {
registerError(execution, serviceName, bpmnError, "infoCall");
} catch (Throwable th) {
registerError(execution, serviceName, th, "infoCall");
}
}
private void registerError(DelegateExecution execution, String serviceName, Throwable th, String variant) {
StringBuilder sb = new StringBuilder();
sb.append(serviceName).append(" ").append(variant).append(" error\n");
sb.append(Exceptions.trimToString(th));
String value = sb.toString();
if (value.length() <= 4000) {
execution.setVariable("call_error", value);
} else {
logger.info("text overflow 4000 chars!");
try {
execution.setVariable("call_error", value.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
}
private void callGws(
String processInstanceId, String componentName,
Client client, ExchangeContext context, ClientRequest clientRequest,
InfoSystemService curService, boolean wrapErrors) {
final Revision revision = client.getRevision();
if (revision == Revision.rev110801) {
throw new UnsupportedOperationException("Revision " + revision + " not supported");
}
String address = StringUtils.trimToNull(curService.getAddress());
if (address != null) {
clientRequest.portAddress = address;
}
if (clientRequest.requestMessage != null) {
ProtocolUtils.fillClientRequestFromSoapMessage(clientRequest);
}
ProtocolUtils.fillServiceRequestPacket(clientRequest, curService);
final ClientProtocol protocol = protocolFactory.createClientProtocol(revision);
if (AdminServiceProvider.getBoolProperty(API.PRODUCTION_MODE)) {
// Реальный контур СМЭВ не принимает тестовые сообщения.
clientRequest.packet.testMsg = null;
}
ClientLog clientLog = null;
final ClientResponse response;
try {
boolean logEnabled = AdminServiceProvider.getBoolProperty(API.ENABLE_CLIENT_LOG) && curService.isLogEnabled();
if (logEnabled || AdminServiceProvider.getBoolProperty(API.LOG_ERRORS)) {
Bid bid = AdminServiceProvider.get().getBidByProcessInstanceId(processInstanceId);
if (bid == null) {
throw new IllegalStateException("Нет номера заявки для процесса '" + processInstanceId + "'");
}
boolean logErrors = AdminServiceProvider.getBoolProperty(API.LOG_ERRORS);
String logStatus = AdminServiceProvider.get().getSystemProperty(API.LOG_STATUS);
Set<String> remote = parseRemote(address);
clientLog = LogCustomizer.createClientLog(bid.getId(), componentName, processInstanceId,
logEnabled, logErrors, logStatus, remote);
}
response = protocol.send(client.getWsdlUrl(), clientRequest, clientLog);
} catch (RuntimeException failure) {
adminService.saveServiceUnavailable(curService);
failure = processFailure(client, context, clientLog, failure);
if (failure == null) {
return;
}
throw wrapErrors ? Exceptions.trim(new SmevBpmnError(SERVER_BPMN_ERROR, failure)) : failure;
} finally {
if (clientLog != null) {
clientLog.close();
}
}
if (wrapErrors) {
try {
client.processClientResponse(response, context);
} catch (BpmnError e) {
throw Exceptions.trim(e); // пользовательские ошибки
} catch (Throwable th) {
throw Exceptions.trim(new SmevBpmnError(SUDDENLY_BPMN_ERROR, th));
}
} else {
client.processClientResponse(response, context);
}
}
public Set<String> parseRemote(String address) {
Set<String> remote = new HashSet<String>();
try {
String host = new URL(address).getHost();
remote.add(host);
InetAddress _address = InetAddress.getByName(host);
String ip = _address.getHostAddress();
remote.add(ip);
} catch (UnknownHostException ignore) {
// будет ошибка снова, сейчас игнорируем
} catch (MalformedURLException ignore) {
// будет ошибка снова, сейчас игнорируем
}
return remote;
}
public RuntimeException processFailure(Client client, ExchangeContext context,
ClientLog clientLog, RuntimeException failure) {
failure = Exceptions.trim(failure);
if (client instanceof ClientFailureAware) {
try {
((ClientFailureAware) client).processFailure(context, failure);
failure = null;
} catch (RuntimeException nested) {
if (setRootCause(nested, failure) && clientLog != null) {
clientLog.log(nested);
}
failure = nested;
}
}
return failure;
}
/**
* Внедрение первопричины.
*/
private boolean setRootCause(RuntimeException nested, RuntimeException root) {
Throwable cause = Exceptions.trim(nested);
boolean rootFound = false;
while (cause.getCause() != null) {
if (cause == root) {
rootFound = true;
}
cause = Exceptions.trim(cause.getCause());
}
if (!rootFound && cause != root) {
try {
Field causeFiled = Throwable.class.getDeclaredField("cause");
causeFiled.setAccessible(true);
causeFiled.set(cause, root);
return true;
} catch (NoSuchFieldException e) {
logger.log(Level.INFO, "can't set cause", e);
} catch (IllegalAccessException e) {
logger.log(Level.INFO, "can't set cause", e);
}
}
return false;
}
public ClientRequestEntity prepare(DelegateExecution execution, String serviceName, String variableName) {
InfoSystemService service = validateAndGetService(serviceName);
final Client client = findByNameAndVersion(serviceName, service.getSversion());
final ExchangeContext context = new ActivitiExchangeContext(execution);
ProtocolUtils.writeInfoSystemsToContext(service, context);
final ClientRequest clientRequest = client.createClientRequest(context);
final ClientRequestEntity entity = createClientRequestEntity(serviceName, clientRequest, client.getRevision());
// Activiti не вызывает сохраниение значений JPA сущноестей,
// это нужня сделать явно.
Activiti.getEm().persist(entity);
logger.fine("Persist CRE " + entity.getId());
if (execution.hasVariable(variableName)) {
execution.removeVariable(variableName);
}
execution.setVariable(variableName, entity);
putEnclosureToContext(clientRequest, context, variableName, execution);
return entity;
}
private void putEnclosureToContext(ClientRequest clientRequest, ExchangeContext context, String variableName, DelegateExecution execution) {
List<String> enclosureVariableNames = new LinkedList<String>();
Enclosure[] enclosures = clientRequest.enclosures;
if (enclosures != null) {
for (int idx = 0; idx < enclosures.length; idx++) {
Enclosure enclosure = enclosures[idx];
String variable = null;
// code может использоваться как имя переменной!
if (enclosure.code != null) {
Enclosure check = context.getEnclosure(enclosure.code);
if (check != null && Arrays.equals(check.content, enclosure.content)) {
variable = enclosure.code;
}
}
if (variable == null) {
variable = variableName + "_enclosure_to_sign_" + idx;
context.addEnclosure(variable, enclosure);
}
enclosureVariableNames.add(variable);
}
}
execution.setVariable(buildVariableNameForStoreEnclosureVars(variableName), Joiner.on(';').join(enclosureVariableNames));
}
private Enclosure[] getEnclosuresFromContext(ExchangeContext context, String variableName) {
final String variableForStoreDynamicEnclosures = buildVariableNameForStoreEnclosureVars(variableName);
final String dynamicEnclosuresVars = (String) context.getVariable(variableForStoreDynamicEnclosures);
ImmutableList<String> dynamicEnclosureList = ImmutableList.copyOf(
Splitter.on(';')
.trimResults()
.omitEmptyStrings()
.split(defaultString(dynamicEnclosuresVars))
);
Enclosure[] result = new Enclosure[dynamicEnclosureList.size()];
int idx = 0;
for (String enclosureVarName : dynamicEnclosureList) {
Enclosure enclosure = context.getEnclosure(enclosureVarName);
result[idx++] = enclosure;
}
return result;
}
private String buildVariableNameForStoreEnclosureVars(String variableName) {
return variableName + "_enclosure_to_sign_vars";
}
// TODO убрать serviceName
public void done(DelegateExecution execution, String serviceName, String variableName) {
ExchangeContext context = new ActivitiExchangeContext(execution);
ClientRequestEntity entity = getAndValidateClientRequestEntity(serviceName, variableName, context);
ClientRequest clientRequest = createClientRequest(entity, context, execution.getId(), variableName);
InfoSystemService service = validateAndGetService(entity.name);
Client client = findByNameAndVersion(entity.name, service.getSversion());
callGws(execution.getProcessInstanceId(), serviceName, client, context, clientRequest, service, false);
}
public void result(DelegateExecution execution) {
result(execution, "resultMessage");
}
public void result(DelegateExecution execution, String message) {
Object serverResponseEntityId = execution.getVariable(SERVER_RESPONSE_ID);
if (serverResponseEntityId != null && !serverResponseEntityId.toString().isEmpty()) {
return;
}
Bid bid = getBid(execution);
ExternalGlue glue = bid.getGlue();
if (glue == null) {
throw new BpmnError("Нет связи с внешней услугой");
}
TRef<Server> ref = serviceRegistry.getServerByName(glue.getName());
if (ref == null) {
throw new BpmnError("Услуга не найдена: имя " + glue.getName());
}
Server service = ref.getRef();
ActivitiReceiptContext exchangeContext = new ActivitiReceiptContext(execution, bid.getId());
ServerResponse response = service.processResult(message, exchangeContext);
adminService.saveServiceResponse(
new ServiceResponseEntity(bid, response),
response.attachmens,
exchangeContext.getUsedEnclosures());
}
public void completeReceipt(DelegateExecution delegateExecution, String rejectReason) {
Bid bid = getBid(delegateExecution);
ExternalGlue glue = bid.getGlue();
if (glue != null && adminService.countOfServerResponseByBidIdAndStatus(bid.getId(), Packet.Status.RESULT.name()) == 0) {
TRef<Server> ref = serviceRegistry.getServerByName(glue.getName());
Server service = ref.getRef();
ActivitiReceiptContext exchangeContext = new ActivitiReceiptContext(delegateExecution, bid.getId());
ServerResponse response;
if (rejectReason != null && service instanceof ServerRejectAware) {
response = ((ServerRejectAware) service).processReject(rejectReason, exchangeContext);
} else {
String msg = rejectReason == null ? "Исполнено" : ("Удалено: " + rejectReason);
response = service.processResult(msg, exchangeContext);
}
if (response == null) {
throw new BpmnError(SUDDENLY_BPMN_ERROR, "Поставщик " + glue.getName() + " при вызове метода processResult вернул null");
}
adminService.saveServiceResponse(
new ServiceResponseEntity(bid, response),
response.attachmens,
exchangeContext.getUsedEnclosures());
}
}
public void status(DelegateExecution execution, String statusValue) {
Bid bid = getBid(execution);
ExternalGlue glue = bid.getGlue();
if (glue == null) {
throw new BpmnError("Нет связи с внешней услугой");
}
TRef<Server> ref = serviceRegistry.getServerByName(glue.getName());
if (ref == null) {
throw new BpmnError("Услуга не найдена: имя " + glue.getName());
}
Server service = ref.getRef();
ActivitiReceiptContext exchangeContext = new ActivitiReceiptContext(execution, bid.getId());
ServerResponse response = service.processStatus(statusValue, exchangeContext);
adminService.saveServiceResponse(new ServiceResponseEntity(bid, response),
response.attachmens,
exchangeContext.getUsedEnclosures());
}
private Bid getBid(DelegateExecution execution) {
return adminService.getBidByProcessInstanceId(execution.getProcessInstanceId());
}
private ClientRequestEntity getAndValidateClientRequestEntity(String serviceName, String variableName, ExchangeContext context) {
final Object object = context.getVariable(variableName);
if (object == null) {
throw new BpmnError("СМЭВ запрос " + variableName + " не найден");
}
if (!(object instanceof ClientRequestEntity)) {
throw new BpmnError("Переменная процесса " + variableName + " не является СМЭВ запросом");
}
final ClientRequestEntity entity = (ClientRequestEntity) object;
if (!entity.name.equals(serviceName)) {
throw new BpmnError("СМЭВ запрос " + variableName + " сформирован для сервиса " + entity.name);
}
return entity;
}
public ClientRequest createClientRequest(ClientRequestEntity entity, ExchangeContext context, String executionId, String variableName) {
final ClientRequest request = new ClientRequest();
if (entity.action != null || entity.actionNs != null) {
request.action = new QName(entity.actionNs, entity.action);
}
if (entity.port != null || entity.portNs != null) {
request.port = new QName(entity.portNs, entity.port);
}
if (entity.service != null || entity.serviceNs != null) {
request.service = new QName(entity.serviceNs, entity.service);
}
request.portAddress = entity.portAddress;
request.requestMessage = entity.requestMessage;
request.appData = entity.appData;
request.enclosures = getEnclosuresFromContext(context, variableName);
final Packet packet = new Packet();
request.packet = packet;
packet.typeCode = Packet.Type.valueOf(entity.gservice);
packet.status = Packet.Status.valueOf(entity.status);
packet.date = entity.date;
packet.exchangeType = entity.exchangeType;
packet.requestIdRef = entity.requestIdRef;
packet.originRequestIdRef = entity.originRequestIdRef;
packet.serviceCode = entity.serviceCode;
packet.caseNumber = entity.caseNumber;
packet.testMsg = entity.testMsg;
request.enclosureDescriptor = entity.enclosureDescriptor;
return request;
}
private ClientRequestEntity createClientRequestEntity(String serviceName, ClientRequest request, Revision revision) {
final ClientRequestEntity entity = new ClientRequestEntity();
entity.name = serviceName;
if (request.action != null) {
entity.action = request.action.getLocalPart();
entity.actionNs = request.action.getNamespaceURI();
}
if (request.port != null) {
entity.port = request.port.getLocalPart();
entity.portNs = request.port.getNamespaceURI();
}
if (request.service != null) {
entity.service = request.service.getLocalPart();
entity.serviceNs = request.service.getNamespaceURI();
}
if (request.appData != null) {
if (request.signRequired) {
List<QName> namespaces = new ArrayList<QName>();
//TODO: убрать в протокол?
if (revision == Revision.rev111111) {
namespaces.add(new QName("http://smev.gosuslugi.ru/rev111111", "smev"));
}
final AppData normalize = cryptoProvider.normalize(namespaces, request.appData);
try {
entity.appData = new String(normalize.content, "UTF8");
entity.digest = new String(normalize.digest, "UTF8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
} else {
entity.appData = request.appData;
}
}
entity.portAddress = request.portAddress;
entity.requestMessage = request.requestMessage;
final Packet packet = request.packet;
entity.gservice = packet.typeCode.name();
entity.status = packet.status.name();
entity.date = new Date();
entity.exchangeType = packet.exchangeType;
entity.requestIdRef = packet.requestIdRef;
entity.originRequestIdRef = packet.originRequestIdRef;
entity.serviceCode = packet.serviceCode;
entity.caseNumber = packet.caseNumber;
entity.testMsg = packet.testMsg;
entity.signRequired = request.signRequired;
entity.enclosureDescriptor = request.enclosureDescriptor;
return entity;
}
public Client findByNameAndVersion(String serviceName, String sversion) {
final TRef<Client> clientRef = registry.getClientByNameAndVersion(serviceName, sversion);
if (clientRef == null) {
throw new IllegalStateException("Client not found! [" + serviceName + "]");
}
return clientRef.getRef();
}
public InfoSystemService validateAndGetService(String serviceName) {
List<InfoSystemService> services = adminService.getInfoSystemServiceBySName(serviceName);
if (services == null || services.isEmpty()) {
throw new IllegalStateException("Нет модуля потребителя СМЭВ с именем '" + serviceName + "'");
}
return getServiceWithMaxVersion(services);
}
private InfoSystemService getServiceWithMaxVersion(List<InfoSystemService> services) {
InfoSystemService curService = null;
for (InfoSystemService s : services) {
if (curService == null) {
curService = s;
}
if (s.getSversion().compareTo(curService.getSversion()) >= 0) {
curService = s;
}
}
return curService;
}
public ru.codeinside.adm.database.InfoSystem getDefaultSender() {
return adminService.getMainInfoSystem();
}
public ClientProtocol createProtocol(Revision revision) {
return protocolFactory.createClientProtocol(revision);
}
public void storeUnavailable(InfoSystemService service) {
adminService.saveServiceUnavailable(service);
}
}