Added MonitoringRequest entity; Added validation for MonitoringRequest; Added unmarshalling for XmlProcessor; Added unmarshalling for MonitoringRequest
This commit is contained in:
parent
97cdefd166
commit
ace8bd46e4
12 changed files with 326 additions and 32 deletions
65
monitoring-request.xsd
Normal file
65
monitoring-request.xsd
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element name="MonitoringRequest">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="UserId" type="RequiredString"/>
|
||||
<xs:element name="Password" type="RequiredString"/>
|
||||
<xs:element name="PackageName" type="PackageNameType"/>
|
||||
<xs:element name="Subjects">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Subject" type="Subject" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- Проверка уникальности Id в рамках всего XML -->
|
||||
<xs:unique name="uniqueSubjectId">
|
||||
<xs:selector xpath=".//Subject"/>
|
||||
<xs:field xpath="Id"/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
|
||||
<!-- Тип для Subject -->
|
||||
<xs:complexType name="Subject">
|
||||
<xs:sequence>
|
||||
<xs:element name="Id" type="IdType"/>
|
||||
<xs:element name="ClientId" type="ClientIdType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- Тип для ClientId (ровно 9 цифр) -->
|
||||
<xs:simpleType name="ClientIdType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:length value="9"/>
|
||||
<xs:pattern value="[0-9]{9}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Тип для Id (не более 36 символов) -->
|
||||
<xs:simpleType name="IdType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="36"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Тип для PackageName -->
|
||||
<xs:simpleType name="PackageNameType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="22"/>
|
||||
<xs:pattern value="[A-Z0-9_]+"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- Базовый тип для обязательных строковых полей -->
|
||||
<xs:simpleType name="RequiredString">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
@ -17,4 +17,6 @@ public class CreditTrackerSettings {
|
|||
private DataSourceSettings signalDataSourceSettings;
|
||||
@NestedConfigurationProperty
|
||||
private DataSourceSettings scoringDataSourceSettings;
|
||||
@NestedConfigurationProperty
|
||||
private XsdValidation xsdValidation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.nbch.credit_tracker.config.settings;
|
||||
|
||||
public record XsdValidation (
|
||||
String monitoringRequestPath
|
||||
) {}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
package ru.nbch.credit_tracker.entities.monitoring_request;
|
||||
|
||||
import jakarta.xml.bind.annotation.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"userId",
|
||||
"password",
|
||||
"packageName",
|
||||
"subjects"
|
||||
})
|
||||
@XmlRootElement(name = "MonitoringRequest")
|
||||
public class MonitoringRequest {
|
||||
|
||||
@XmlElement(name = "UserId", required = true)
|
||||
protected String userId;
|
||||
@XmlElement(name = "Password", required = true)
|
||||
protected String password;
|
||||
@XmlElement(name = "PackageName", required = true)
|
||||
protected String packageName;
|
||||
@XmlElement(name = "Subjects", required = true)
|
||||
protected Subjects subjects;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
package ru.nbch.credit_tracker.entities.monitoring_request;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "SubjectType", propOrder = {
|
||||
"id",
|
||||
"clientId"
|
||||
})
|
||||
public class Subject {
|
||||
|
||||
@XmlElement(name = "Id", required = true)
|
||||
protected String id;
|
||||
|
||||
@XmlElement(name = "ClientId", required = true)
|
||||
protected String clientId;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.nbch.credit_tracker.entities.monitoring_request;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"subject"
|
||||
})
|
||||
public class Subjects {
|
||||
|
||||
@XmlElement(name = "Subject", required = true)
|
||||
protected List<Subject> subject;
|
||||
|
||||
public List<Subject> getSubject() {
|
||||
if (subject == null) {
|
||||
subject = new ArrayList<>();
|
||||
}
|
||||
return this.subject;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.nbch.credit_tracker.exceptions;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class ValidationException extends Exception {
|
||||
|
||||
private String elementName;
|
||||
|
||||
public ValidationException() {
|
||||
}
|
||||
|
||||
public ValidationException(String elementName) {
|
||||
super();
|
||||
this.elementName = elementName;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
package ru.nbch.credit_tracker.facade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.PathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
|
||||
import ru.nbch.credit_tracker.entities.monitoring_request.MonitoringRequest;
|
||||
import ru.nbch.credit_tracker.enums.SignalError;
|
||||
import ru.nbch.credit_tracker.exceptions.ValidationException;
|
||||
import ru.nbch.credit_tracker.service.FailureAnswerGenerator;
|
||||
import ru.nbch.credit_tracker.service.scoring.AuthorizationService;
|
||||
import ru.nbch.credit_tracker.service.xml.IXmlProcessor;
|
||||
import ru.nbch.credit_tracker.service.xml.XmlUtilService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
|
|
@ -22,16 +23,23 @@ import java.util.zip.ZipInputStream;
|
|||
public class SignalFacade {
|
||||
private static final long MAX_XML_FILE_SIZE = 2L * 1024 * 1024 * 1024;
|
||||
|
||||
private final CreditTrackerSettings config;
|
||||
private final AuthorizationService authorizationService;
|
||||
private final FailureAnswerGenerator failureAnswerGenerator;
|
||||
private final XmlUtilService xmlUtilService;
|
||||
|
||||
public SignalFacade(AuthorizationService authorizationService,
|
||||
FailureAnswerGenerator failureAnswerGenerator) {
|
||||
public SignalFacade(CreditTrackerSettings config,
|
||||
AuthorizationService authorizationService,
|
||||
FailureAnswerGenerator failureAnswerGenerator,
|
||||
XmlUtilService xmlUtilService) {
|
||||
this.config = config;
|
||||
this.authorizationService = authorizationService;
|
||||
this.failureAnswerGenerator = failureAnswerGenerator;
|
||||
this.xmlUtilService = xmlUtilService;
|
||||
}
|
||||
|
||||
public ResponseEntity<Resource> setupMonitoring(MultipartFile file) {
|
||||
// первичные валидации
|
||||
if (file.isEmpty()) {
|
||||
return getBadResponse(SignalError.ERROR_001);
|
||||
}
|
||||
|
|
@ -40,6 +48,7 @@ public class SignalFacade {
|
|||
return getBadResponse(SignalError.ERROR_001);
|
||||
}
|
||||
|
||||
// Валидация Архива на количетсво файлов, размер и расширение
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
|
||||
int fileCount = 0;
|
||||
ZipEntry xmlEntry = null;
|
||||
|
|
@ -72,14 +81,36 @@ public class SignalFacade {
|
|||
return getBadResponse(SignalError.ERROR_009);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorPath = failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001);
|
||||
return ResponseEntity.internalServerError().body(new PathResource(errorPath));
|
||||
}
|
||||
|
||||
AuthInfoData authInfoData = parseAuthInfo(zipInputStream);
|
||||
if (!authorizationService.hasAuthorization(authInfoData.getUserId(), authInfoData.getPassword())) {
|
||||
// Теперь знаем, что в архиве лежит ровно один файл с раширением xml и он весит меньше 2гб. Можно валидировать сам xml
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
|
||||
zipInputStream.getNextEntry();
|
||||
validateRequestByXsd(zipInputStream);
|
||||
|
||||
} catch (ValidationException e) {
|
||||
return getBadResponse(SignalError.ERROR_011); // TODO вычленять конкретный элемент
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorPath = failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001);
|
||||
return ResponseEntity.internalServerError().body(new PathResource(errorPath));
|
||||
}
|
||||
|
||||
// Xml валиден. Производим анмаршаллинг
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
|
||||
zipInputStream.getNextEntry();
|
||||
|
||||
IXmlProcessor processor = xmlUtilService.createJaxbProcessor();
|
||||
MonitoringRequest monitoringRequest = processor.read(zipInputStream, MonitoringRequest.class);
|
||||
|
||||
if (!authorizationService.hasAuthorization(monitoringRequest.getUserId(), monitoringRequest.getPassword())) {
|
||||
return getBadResponse(SignalError.ERROR_005);
|
||||
}
|
||||
|
||||
// TODO process xml file
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorPath = failureAnswerGenerator.generateFailureAnswer(SignalError.ERROR_001);
|
||||
|
|
@ -89,6 +120,13 @@ public class SignalFacade {
|
|||
return ResponseEntity.ok().body(new PathResource("D:\\folder_name\\legal.xml")); // TODO generate success response
|
||||
}
|
||||
|
||||
private void validateRequestByXsd(ZipInputStream inputStream) throws ValidationException {
|
||||
try {
|
||||
xmlUtilService.validateXMLByXSD(inputStream, config.getXsdValidation().monitoringRequestPath());
|
||||
} catch (Exception e) {
|
||||
throw new ValidationException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isZipFile(MultipartFile file) {
|
||||
return file.getContentType() != null &&
|
||||
|
|
@ -96,21 +134,8 @@ public class SignalFacade {
|
|||
file.getOriginalFilename() != null && file.getOriginalFilename().toLowerCase().endsWith(".zip");
|
||||
}
|
||||
|
||||
private AuthInfoData parseAuthInfo(ZipInputStream zipInputStream) throws IOException {
|
||||
return new AuthInfoData("0001XX000001", "test"); // TODO parse
|
||||
}
|
||||
|
||||
|
||||
private ResponseEntity<Resource> getBadResponse(SignalError error) {
|
||||
String errorPath = failureAnswerGenerator.generateFailureAnswer(error);
|
||||
return ResponseEntity.badRequest().body(new PathResource(errorPath));
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
private static class AuthInfoData {
|
||||
private String userId;
|
||||
private String password;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
package ru.nbch.credit_tracker.service.xml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface IXmlProcessor {
|
||||
String asString(Object xmlObj, boolean useStandalone);
|
||||
|
||||
void write(String path, Object xmlObj, boolean useStandalone);
|
||||
|
||||
<Z> Z read(InputStream inputStream, Class<Z> clazz);
|
||||
|
||||
<Z> Z read(Path xml, Class<Z> clazz);
|
||||
|
||||
<Z> Z read(String xml, Class<Z> clazz);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,32 @@ package ru.nbch.credit_tracker.service.xml;
|
|||
import jakarta.xml.bind.JAXBContext;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Marshaller;
|
||||
import jakarta.xml.bind.Unmarshaller;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.eclipse.persistence.jaxb.MarshallerProperties;
|
||||
import ru.nbch.credit_tracker.utils.EscapeHandlerUtil;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
import javax.xml.validation.Schema;
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public abstract class XmlProcessorImplBase implements IXmlProcessor {
|
||||
protected String charsetName;
|
||||
private String charsetName;
|
||||
private XMLInputFactory xmlInputFactory;
|
||||
private Map<Class<?>, Schema> schemasMap = new HashMap<>();
|
||||
|
||||
public XmlProcessorImplBase() {
|
||||
this.charsetName = "windows-1251";
|
||||
}
|
||||
|
||||
public XmlProcessorImplBase(String charsetName) {
|
||||
this();
|
||||
this.xmlInputFactory = XMLInputFactory.newInstance();
|
||||
this.xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
|
||||
this.xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
||||
|
||||
this.charsetName = charsetName;
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +60,39 @@ public abstract class XmlProcessorImplBase implements IXmlProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Z> Z read(InputStream inputStream, Class<Z> clazz) {
|
||||
try {
|
||||
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStream);
|
||||
return genUnmarshaller(clazz).unmarshal(xmlStreamReader, clazz).getValue();
|
||||
} catch (Exception e) {
|
||||
log.error(ExceptionUtils.getStackTrace(e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Z> Z read(Path xml, Class<Z> clazz) {
|
||||
try {
|
||||
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(new FileReader(xml.toFile()));
|
||||
return genUnmarshaller(clazz).unmarshal(xmlStreamReader, clazz).getValue();
|
||||
} catch (Exception e) {
|
||||
log.error(ExceptionUtils.getStackTrace(e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Z> Z read(String xml, Class<Z> clazz) {
|
||||
try {
|
||||
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(new StringReader(xml));
|
||||
return genUnmarshaller(clazz).unmarshal(xmlStreamReader, clazz).getValue();
|
||||
} catch (Exception e) {
|
||||
log.error(ExceptionUtils.getStackTrace(e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Marshaller genMarshaller(Object obj, boolean useStandalone) throws JAXBException {
|
||||
Marshaller marshaller = getContextByObject(obj).createMarshaller();
|
||||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
|
||||
|
|
@ -66,6 +107,15 @@ public abstract class XmlProcessorImplBase implements IXmlProcessor {
|
|||
return marshaller;
|
||||
}
|
||||
|
||||
private <G> Unmarshaller genUnmarshaller(Class<G> clazz) throws JAXBException {
|
||||
Unmarshaller unmarshaller = getContextByClass(clazz).createUnmarshaller();
|
||||
Schema schema;
|
||||
if (schemasMap != null && (schema = schemasMap.get(clazz)) != null) {
|
||||
unmarshaller.setSchema(schema);
|
||||
}
|
||||
return unmarshaller;
|
||||
}
|
||||
|
||||
protected JAXBContext getContextByObject(Object obj) throws JAXBException {
|
||||
Class<?> aClass = obj.getClass();
|
||||
return getContextByClass(aClass);
|
||||
|
|
|
|||
|
|
@ -2,16 +2,48 @@ package ru.nbch.credit_tracker.service.xml;
|
|||
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.xml.sax.SAXException;
|
||||
import ru.nbch.credit_tracker.entities.monitoring_request.MonitoringRequest;
|
||||
import ru.nbch.credit_tracker.entities.response.errors.MonitoringError;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
import javax.xml.transform.stax.StAXSource;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
|
||||
|
||||
@Service
|
||||
public class XmlUtilService {
|
||||
|
||||
public IXmlProcessor createJaxbProcessor() throws JAXBException {
|
||||
return new XmlProcessorImplAnyClasses(Charset.forName("windows-1251"),
|
||||
// unmarshalling classes
|
||||
MonitoringRequest.class,
|
||||
// marshalling classes
|
||||
MonitoringError.class);
|
||||
}
|
||||
|
||||
public void validateXMLByXSD(InputStream xmlStream, String xsdPath) throws IOException, XMLStreamException, SAXException {
|
||||
SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
|
||||
Schema schema = sf.newSchema(new File(xsdPath));
|
||||
|
||||
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
|
||||
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
||||
xmlInputFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
xmlInputFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
||||
|
||||
Validator validator = schema.newValidator();
|
||||
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(xmlStream);
|
||||
validator.validate(new StAXSource(xmlStreamReader));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,4 +22,7 @@ ct.scoring-data-source-settings.password=tester
|
|||
|
||||
# TODO Increase file size limit if needed
|
||||
#spring.servlet.multipart.max-file-size=10MB
|
||||
#spring.servlet.multipart.max-request-size=10MB
|
||||
#spring.servlet.multipart.max-request-size=10MB
|
||||
|
||||
# xsd paths
|
||||
ct.xsd-validation.monitoring-request-path=monitoring-request.xsd
|
||||
Loading…
Add table
Reference in a new issue