Added JAXB; Added XmlProcessor; Added FailureAnswerGenerator functionality

This commit is contained in:
Mikhail Trofimov 2025-06-04 16:23:08 +03:00
parent 62e60dd909
commit 590baeba97
10 changed files with 377 additions and 3 deletions

29
pom.xml
View file

@ -37,12 +37,39 @@
<artifactId>jcc</artifactId> <artifactId>jcc</artifactId>
<version>12.1.0.0</version> <version>12.1.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.18.0</version>
</dependency>
<!-- JAXB API -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<!--JAXB runtime implementation: eclipse moxy-->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>4.0.2</version>
</dependency>
<!-- Activation dependency needed for JAXB -->
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
<version>2.0.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View file

@ -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;
}

View file

@ -1,13 +1,55 @@
package ru.nbch.credit_tracker.service; 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 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.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 @Service
public class FailureAnswerGenerator { 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) { 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;
}
} }

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"yes\"?>\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("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"yes\"?>\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;
}

View file

@ -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);
}
}

View file

@ -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("&quot;");
} else {
out.write(34);
}
break;
case '&':
out.write("&amp;");
break;
case '<':
out.write("&lt;");
break;
case '>':
out.write("&gt;");
break;
default:
if (ch[i] > 127) {
out.write("&#");
out.write(Integer.toString(ch[i]));
out.write(59);
} else {
out.write(ch[i]);
}
}
}
}
}

View file

@ -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 <artifactId>eclipselink</artifactId> 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);
}
};
}
}

View file

@ -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("&quot;");
} else {
out.write(34);
}
break;
case '&':
out.write("&amp;");
break;
case '<':
out.write("&lt;");
break;
case '>':
out.write("&gt;");
break;
default:
if (this.encoder.canEncode(ch[i])) {
out.write(ch[i]);
} else {
out.write("&#");
out.write(Integer.toString(ch[i]));
out.write(59);
}
}
}
}
}