From 590baeba974b82e4cae2c69afe346906e677c916 Mon Sep 17 00:00:00 2001 From: Mikhail Trofimov Date: Wed, 4 Jun 2025 16:23:08 +0300 Subject: [PATCH] Added JAXB; Added XmlProcessor; Added FailureAnswerGenerator functionality --- pom.xml | 29 ++++++- .../response/errors/MonitoringError.java | 25 ++++++ .../service/FailureAnswerGenerator.java | 46 ++++++++++- .../service/xml/IXmlProcessor.java | 7 ++ .../xml/XmlProcessorImplAnyClasses.java | 22 ++++++ .../service/xml/XmlProcessorImplBase.java | 76 +++++++++++++++++++ .../service/xml/XmlUtilService.java | 17 +++++ .../utils/DumbEscapeHandler.java | 48 ++++++++++++ .../utils/EscapeHandlerUtil.java | 58 ++++++++++++++ .../utils/NioEscapeHandler.java | 52 +++++++++++++ 10 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/nbch/credit_tracker/entities/response/errors/MonitoringError.java create mode 100644 src/main/java/ru/nbch/credit_tracker/service/xml/IXmlProcessor.java create mode 100644 src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplAnyClasses.java create mode 100644 src/main/java/ru/nbch/credit_tracker/service/xml/XmlProcessorImplBase.java create mode 100644 src/main/java/ru/nbch/credit_tracker/service/xml/XmlUtilService.java create mode 100644 src/main/java/ru/nbch/credit_tracker/utils/DumbEscapeHandler.java create mode 100644 src/main/java/ru/nbch/credit_tracker/utils/EscapeHandlerUtil.java create mode 100644 src/main/java/ru/nbch/credit_tracker/utils/NioEscapeHandler.java 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); + } + } + } + + } +} + +