diff --git a/pom.xml b/pom.xml
index 4a91fcc..28eec5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,12 +37,39 @@
jcc
12.1.0.0
-
org.projectlombok
lombok
true
+
+ org.apache.commons
+ commons-lang3
+ 3.17.0
+
+
+ commons-io
+ commons-io
+ 2.18.0
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ 4.0.2
+
+
+
+ org.eclipse.persistence
+ org.eclipse.persistence.moxy
+ 4.0.2
+
+
+
+ com.sun.activation
+ jakarta.activation
+ 2.0.1
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/ru/nbch/credit_tracker/entities/response/errors/MonitoringError.java b/src/main/java/ru/nbch/credit_tracker/entities/response/errors/MonitoringError.java
new file mode 100644
index 0000000..f822c3b
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/entities/response/errors/MonitoringError.java
@@ -0,0 +1,25 @@
+package ru.nbch.credit_tracker.entities.response.errors;
+
+import jakarta.xml.bind.annotation.*;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "MonitoringErrorResponse", propOrder = {
+ "status",
+ "errorCode",
+ "errorText"
+})
+@XmlRootElement(name = "MonitoringErrorResponse")
+public class MonitoringError {
+
+ @XmlElement(name = "Status", required = true)
+ protected String status;
+ @XmlElement(name = "ErrorCode",required = true)
+ protected String errorCode;
+ @XmlElement(name = "ErrorText",required = true)
+ protected String errorText;
+
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java b/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java
index 67c9643..f8873bd 100644
--- a/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java
+++ b/src/main/java/ru/nbch/credit_tracker/service/FailureAnswerGenerator.java
@@ -1,13 +1,55 @@
package ru.nbch.credit_tracker.service;
+import jakarta.xml.bind.JAXBException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Service;
+import ru.nbch.credit_tracker.entities.response.errors.MonitoringError;
import ru.nbch.credit_tracker.enums.SignalError;
+import ru.nbch.credit_tracker.service.xml.IXmlProcessor;
+import ru.nbch.credit_tracker.service.xml.XmlUtilService;
-// TODO generate xml error file (mb return path ?)
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.UUID;
+
+@Slf4j
@Service
public class FailureAnswerGenerator {
+ private static final String ANSWER_PATH = "temp_failure_response";
+ private final XmlUtilService xmlUtilService;
+
+ public FailureAnswerGenerator(XmlUtilService xmlUtilService) {
+ this.xmlUtilService = xmlUtilService;
+ }
public String generateFailureAnswer(SignalError error) {
- return error.getCode() + ": " + error.getMessage();
+ try {
+ File tempFolder = new File(ANSWER_PATH);
+ if (!tempFolder.exists()) {
+ tempFolder.mkdir();
+ }
+ String answerPath = ANSWER_PATH + File.separator + UUID.randomUUID() + File.separator + "error_reply.xml";
+
+ IXmlProcessor xmlProcessor = xmlUtilService.createJaxbProcessor();
+ String errorMessage = xmlProcessor.asString(createErrorMessage(error), false);
+ FileUtils.writeStringToFile(new File(answerPath), errorMessage, Charset.forName("windows-1251"));
+
+ return answerPath;
+ } catch (IOException | JAXBException e) {
+ log.error(e.getMessage(), e);
+ }
+ return null;
}
+
+ // TODO think about common error class
+ private MonitoringError createErrorMessage(SignalError error) {
+ MonitoringError errorMessage = new MonitoringError();
+ errorMessage.setStatus("error");
+ errorMessage.setErrorCode(error.getCode());
+ errorMessage.setErrorText(error.getMessage());
+ return errorMessage;
+ }
+
}
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
new file mode 100644
index 0000000..8145598
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java
@@ -0,0 +1,7 @@
+package ru.nbch.credit_tracker.service.xml;
+
+public interface IXmlProcessor {
+ String asString(Object xmlObj, boolean useStandalone);
+
+ void write(String path, Object xmlObj, boolean useStandalone);
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplAnyClasses.java b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplAnyClasses.java
new file mode 100644
index 0000000..bc27c32
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplAnyClasses.java
@@ -0,0 +1,22 @@
+package ru.nbch.credit_tracker.service.xml;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBException;
+import org.eclipse.persistence.jaxb.JAXBContextFactory;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+
+public class XmlProcessorImplAnyClasses extends XmlProcessorImplBase {
+ private final JAXBContext context;
+
+ public XmlProcessorImplAnyClasses(Charset charset, Class>... classes) throws JAXBException {
+ super(charset.name());
+ this.context = JAXBContextFactory.createContext(classes, new HashMap<>());
+ }
+
+ @Override
+ protected JAXBContext getContextByClass(Class> clazz) throws JAXBException {
+ return context;
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..227a9c9
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java
@@ -0,0 +1,76 @@
+package ru.nbch.credit_tracker.service.xml;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import lombok.extern.slf4j.Slf4j;
+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;
+
+@Slf4j
+public abstract class XmlProcessorImplBase implements IXmlProcessor {
+ protected String charsetName;
+
+ public XmlProcessorImplBase() {
+ this.charsetName = "windows-1251";
+ }
+
+ public XmlProcessorImplBase(String charsetName) {
+ this();
+ this.charsetName = charsetName;
+ }
+
+ @Override
+ public String asString(Object xmlObj, boolean useStandalone) {
+ try {
+ StringWriter strWriter = new StringWriter();
+ if (useStandalone) {
+ strWriter.write("\n".formatted(charsetName));
+ }
+ genMarshaller(xmlObj, useStandalone).marshal(xmlObj, strWriter);
+ return strWriter.toString();
+ } catch (Throwable e) {
+ log.error("Error serializing object to XML string: {}", e.getMessage());
+ throw new IllegalStateException("cannot serialize object to XML");
+ }
+ }
+
+ @Override
+ public void write(String path, Object xmlObj, boolean useStandalone) {
+ try (OutputStream os = new FileOutputStream(path)) {
+ if (useStandalone) {
+ os.write("\n".formatted(charsetName).getBytes(charsetName));
+ }
+ genMarshaller(xmlObj, useStandalone).marshal(xmlObj, os);
+ } catch (Throwable e) {
+ log.error("Error serializing object to XML string: {}", e.getMessage(), e);
+ throw new IllegalStateException("cannot serialize object to XML");
+ }
+ }
+
+ public Marshaller genMarshaller(Object obj, boolean useStandalone) throws JAXBException {
+ Marshaller marshaller = getContextByObject(obj).createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ marshaller.setProperty(Marshaller.JAXB_ENCODING, charsetName);
+ if (useStandalone) {
+ marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
+ } else {
+ marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE);
+ }
+ marshaller.setProperty(MarshallerProperties.CHARACTER_ESCAPE_HANDLER, EscapeHandlerUtil.createEscapeHandler(marshaller));
+ marshaller.setProperty(MarshallerProperties.INDENT_STRING, " ");
+ return marshaller;
+ }
+
+ protected JAXBContext getContextByObject(Object obj) throws JAXBException {
+ Class> aClass = obj.getClass();
+ return getContextByClass(aClass);
+ }
+
+ protected abstract JAXBContext getContextByClass(Class> clazz) throws JAXBException;
+}
+
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
new file mode 100644
index 0000000..3bdadbc
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java
@@ -0,0 +1,17 @@
+package ru.nbch.credit_tracker.service.xml;
+
+import jakarta.xml.bind.JAXBException;
+import org.springframework.stereotype.Service;
+import ru.nbch.credit_tracker.entities.response.errors.MonitoringError;
+
+import java.nio.charset.Charset;
+
+@Service
+public class XmlUtilService {
+
+ public IXmlProcessor createJaxbProcessor() throws JAXBException {
+ return new XmlProcessorImplAnyClasses(Charset.forName("windows-1251"),
+ MonitoringError.class);
+ }
+
+}
diff --git a/src/main/java/ru/nbch/credit_tracker/utils/DumbEscapeHandler.java b/src/main/java/ru/nbch/credit_tracker/utils/DumbEscapeHandler.java
new file mode 100644
index 0000000..4e09c0c
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/utils/DumbEscapeHandler.java
@@ -0,0 +1,48 @@
+package ru.nbch.credit_tracker.utils;
+
+import org.eclipse.persistence.oxm.CharacterEscapeHandler;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class DumbEscapeHandler implements CharacterEscapeHandler {
+ public static final CharacterEscapeHandler theInstance = new DumbEscapeHandler();
+
+ private DumbEscapeHandler() {
+ }
+
+ public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
+ int limit = start + length;
+
+ for (int i = start; i < limit; ++i) {
+ switch (ch[i]) {
+ case '"':
+ if (isAttVal) {
+ out.write(""");
+ } else {
+ out.write(34);
+ }
+ break;
+ case '&':
+ out.write("&");
+ break;
+ case '<':
+ out.write("<");
+ break;
+ case '>':
+ out.write(">");
+ break;
+ default:
+ if (ch[i] > 127) {
+ out.write("");
+ out.write(Integer.toString(ch[i]));
+ out.write(59);
+ } else {
+ out.write(ch[i]);
+ }
+ }
+ }
+
+ }
+}
+
diff --git a/src/main/java/ru/nbch/credit_tracker/utils/EscapeHandlerUtil.java b/src/main/java/ru/nbch/credit_tracker/utils/EscapeHandlerUtil.java
new file mode 100644
index 0000000..1f3d675
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/utils/EscapeHandlerUtil.java
@@ -0,0 +1,58 @@
+package ru.nbch.credit_tracker.utils;
+
+
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.PropertyException;
+import org.eclipse.persistence.internal.oxm.record.CharacterEscapeHandlerWrapper;
+import org.eclipse.persistence.jaxb.MarshallerProperties;
+import org.eclipse.persistence.oxm.CharacterEscapeHandler;
+//!!! was in eclipselink dependency
+//import org.glassfish.jaxb.core.marshaller.DumbEscapeHandler;
+//import org.glassfish.jaxb.core.marshaller.MinimumEscapeHandler;
+
+public class EscapeHandlerUtil {
+ public static CharacterEscapeHandler createEscapeHandler(Marshaller marshaller) throws PropertyException {
+ org.eclipse.persistence.internal.oxm.CharacterEscapeHandler handler = null;
+ String encoding = (String) marshaller.getProperty(Marshaller.JAXB_ENCODING);
+ Object escapeHandler = marshaller.getProperty(MarshallerProperties.CHARACTER_ESCAPE_HANDLER);
+ if (encoding == null) encoding = "UTF-8";
+ try {
+ if (escapeHandler != null) {
+ try {
+ handler = (CharacterEscapeHandler) escapeHandler;
+ } catch (Exception ignored) {
+ handler = new CharacterEscapeHandlerWrapper(escapeHandler);
+ }
+ }
+ else {
+ handler = new NioEscapeHandler(encoding);
+ }
+ } catch (Exception ignored) {
+ handler = DumbEscapeHandler.theInstance;
+ }
+ var finalHandler = handler;
+ return (ch, start, length, isAttVal, out) -> {
+ int limit = start + length;
+ int startToWrite = start;
+ int endToWrite;
+ for (int i = start; i < limit; ++i) {
+ if (ch[i] == '<' || ch[i] == '>') {
+ if (i > start) {
+ endToWrite = i;//1;
+ finalHandler.escape(ch, startToWrite, endToWrite - startToWrite, isAttVal, out);
+ startToWrite = i + 1;
+ } else {
+ startToWrite++;
+ }
+ out.write(ch[i]);
+ }
+ }
+ if (startToWrite < limit) {
+ endToWrite = limit - startToWrite;
+ finalHandler.escape(ch, startToWrite, endToWrite, isAttVal, out);
+ }
+ };
+ }
+
+}
+
diff --git a/src/main/java/ru/nbch/credit_tracker/utils/NioEscapeHandler.java b/src/main/java/ru/nbch/credit_tracker/utils/NioEscapeHandler.java
new file mode 100644
index 0000000..b2671bb
--- /dev/null
+++ b/src/main/java/ru/nbch/credit_tracker/utils/NioEscapeHandler.java
@@ -0,0 +1,52 @@
+package ru.nbch.credit_tracker.utils;
+
+import org.eclipse.persistence.internal.oxm.CharacterEscapeHandler;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+public class NioEscapeHandler implements CharacterEscapeHandler {
+ private final CharsetEncoder encoder;
+
+ public NioEscapeHandler(String charsetName) {
+ this.encoder = Charset.forName(charsetName).newEncoder();
+ }
+
+ public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
+ int limit = start + length;
+
+ for(int i = start; i < limit; ++i) {
+ switch (ch[i]) {
+ case '"':
+ if (isAttVal) {
+ out.write(""");
+ } else {
+ out.write(34);
+ }
+ break;
+ case '&':
+ out.write("&");
+ break;
+ case '<':
+ out.write("<");
+ break;
+ case '>':
+ out.write(">");
+ break;
+ default:
+ if (this.encoder.canEncode(ch[i])) {
+ out.write(ch[i]);
+ } else {
+ out.write("");
+ out.write(Integer.toString(ch[i]));
+ out.write(59);
+ }
+ }
+ }
+
+ }
+}
+
+