diff --git a/monitoring-request.xsd b/monitoring-request.xsd
new file mode 100644
index 0000000..e89d9f2
--- /dev/null
+++ b/monitoring-request.xsd
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/ru/nbch/credit_tracker/config/settings/CreditTrackerSettings.java b/src/main/java/ru/nbch/credit_tracker/config/settings/CreditTrackerSettings.java
index 71c1a1b..146d130 100644
--- a/src/main/java/ru/nbch/credit_tracker/config/settings/CreditTrackerSettings.java
+++ b/src/main/java/ru/nbch/credit_tracker/config/settings/CreditTrackerSettings.java
@@ -17,4 +17,6 @@ public class CreditTrackerSettings {
private DataSourceSettings signalDataSourceSettings;
@NestedConfigurationProperty
private DataSourceSettings scoringDataSourceSettings;
+ @NestedConfigurationProperty
+ private XsdValidation xsdValidation;
}
diff --git a/src/main/java/ru/nbch/credit_tracker/config/settings/XsdValidation.java b/src/main/java/ru/nbch/credit_tracker/config/settings/XsdValidation.java
new file mode 100644
index 0000000..5749fa6
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/config/settings/XsdValidation.java
@@ -0,0 +1,5 @@
+package ru.nbch.credit_tracker.config.settings;
+
+public record XsdValidation (
+ String monitoringRequestPath
+) {}
diff --git a/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/MonitoringRequest.java b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/MonitoringRequest.java
new file mode 100644
index 0000000..fba8663
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/MonitoringRequest.java
@@ -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;
+
+
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subject.java b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subject.java
new file mode 100644
index 0000000..0c42a01
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subject.java
@@ -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;
+
+
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subjects.java b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subjects.java
new file mode 100644
index 0000000..bdd68a8
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/entities/monitoring_request/Subjects.java
@@ -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;
+
+ public List getSubject() {
+ if (subject == null) {
+ subject = new ArrayList<>();
+ }
+ return this.subject;
+ }
+
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/exceptions/ValidationException.java b/src/main/java/ru/nbch/credit_tracker/exceptions/ValidationException.java
new file mode 100644
index 0000000..edefe8b
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/exceptions/ValidationException.java
@@ -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;
+ }
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java b/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java
index 5d46661..6e5a49d 100644
--- a/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java
+++ b/src/main/java/ru/nbch/credit_tracker/facade/SignalFacade.java
@@ -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 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 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;
- }
}
diff --git a/src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java b/src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java
index 8145598..cb29054 100644
--- a/src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java
@@ -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 read(InputStream inputStream, Class clazz);
+
+ Z read(Path xml, Class clazz);
+
+ Z read(String xml, Class clazz);
}
diff --git a/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java
index 227a9c9..bf11863 100644
--- a/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java
@@ -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, 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 read(InputStream inputStream, Class 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 read(Path xml, Class 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 read(String xml, Class 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 Unmarshaller genUnmarshaller(Class 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);
diff --git a/src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java
index 3bdadbc..c80d6cf 100644
--- a/src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java
@@ -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));
+ }
+
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 14d9b0d..8bc9690 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -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
\ No newline at end of file
+#spring.servlet.multipart.max-request-size=10MB
+
+# xsd paths
+ct.xsd-validation.monitoring-request-path=monitoring-request.xsd
\ No newline at end of file