Reputation: 437
I'm working on a Spring-based application that uses jPOS to handle ISO8583 messages. I want is to allow scripts (in JavaScript or Python) to modify these ISO messages. To achieve this, I have set up a mechanism to execute scripts that can alter the ISO messages.
However, I am facing an issue when trying to pass data received from an HTTP request (via a Spring controller) to the jPOS filter, as the filter is invoked by jPOS itself.
mport jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.iso.ISOChannel;
import org.jpos.iso.ISOException;
import org.jpos.iso.ISOFilter;
import org.jpos.iso.ISOMsg;
import org.jpos.util.LogEvent;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@Slf4j
public class MessageModifyFilter implements ISOFilter, Configurable {
private String requestScriptPath;
private String responseScriptPath;
private String loadScript(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(path)));
}
@Override
public ISOMsg filter(ISOChannel channel, ISOMsg m, LogEvent logEvent) throws ISOFilter.VetoException {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String scriptPath = isRequest(m) ? requestScriptPath : responseScriptPath;
String script = loadScript(scriptPath);
String functionName = isRequest(m) ? "modifyRequest" : "modifyResponse";
log.info("Applying " + functionName + " script");
return (ISOMsg) ScriptExecutor.executeScript(script, functionName, m, scriptPath, request.getAttribute("EXPOSED_PARAMS"));
} catch (IOException e) {
log.warn("Possibly failed to execute script or the execution returned null!");
log.warn("Therefore returning original object without modification");
return m;
} catch (ISOException e) {
log.info("There was error processing the ISOFilter", e);
log.warn("Therefore returning original object without modification");
return m;
}
}
private boolean isRequest(ISOMsg m) throws ISOException {
return m.getDirection() == ISOMsg.OUTGOING;
}
@Override
public void setConfiguration(Configuration cfg) throws ConfigurationException {
if (StringUtils.hasText(cfg.get("request-modifier-script-Path"))) {
this.requestScriptPath = cfg.get("request-modifier-script-Path");
}
if (StringUtils.hasText(cfg.get("response-modifier-script-Path"))) {
this.responseScriptPath = cfg.get("response-modifier-script-Path");
}
if (requestScriptPath == null) {
log.warn("---REQUEST MODIFIER SCRIPT FILE NOT PROVIDED---");
}
if (responseScriptPath == null) {
log.warn("---RESPONSE MODIFIER SCRIPT FILE NOT PROVIDED---");
}
}
}
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
@UtilityClass
@Slf4j
public class ScriptExecutor {
private String getScriptType(final String scriptPath) {
int lastDotIndex = scriptPath.lastIndexOf('.');
if (lastDotIndex != -1) {
String extension = scriptPath.substring(lastDotIndex + 1);
if ("py".equals(extension)) {
return "python";
} else if ("js".equals(extension)) {
return "js";
}
}
return "java";
}
public static Object executeScript(final String script, final String functionName, final Object... args) {
;
try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
context.eval(getScriptType((String) args[0]), script);
Value function = context.getBindings(getScriptType((String) args[0])).getMember(functionName);
if (function.canExecute()) {
return function.execute(args).as(Object.class);
} else {
throw new RuntimeException("Function " + functionName + " is not executable please check your script");
}
} catch (Exception e) {
log.info("There was an error executing the message modifier script");
return null;
}
}
}
Problem:
Since jPOS operates outside the HTTP request context, using RequestContextHolder.currentRequestAttributes() to access request-specific data results in the following error:
No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) at com.sanjok.iso8583.MessageModifyFilter.filter(MessageModifyFilter.java:37) at org.jpos.iso.BaseChannel.applyOutgoingFilters(BaseChannel.java:963)
Any guidance or suggestions would be greatly appreciated.
Regarding Andrés Alcarraz`s query,why applying filters when creating the message isn’t ideal for my situation:
Centralized Configuration: I currently have a centralized configuration for both incoming and outgoing messages, like this:
<filter class="com.test.iso8583.MessageModifyFilter" logger="Q2">
<property name="request-modifier-script-Path" value="${catalina.base}/conf/iso8583/request-modifier.js"/>
<property name="response-modifier-script-Path" value="${catalina.base}/conf/iso8583/response-modifier.js"/>
</filter>
Splitting this configuration to different parts of the application would complicate maintenance and reduce coherence.
Middleware Application: My application is a middleware that deals with multiple clients. Some clients do not echo back fields correctly (e.g., sending back 000000111111 instead of 111111). I need to modify the response before qmux makes equality comparison for the provided key field.
Upvotes: 4
Views: 95