package forklift.consumer;
import forklift.classloader.RunAsClassLoader;
import forklift.connectors.ConnectorException;
import forklift.connectors.ForkliftMessage;
import forklift.producers.ForkliftProducerI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MessageRunnable implements Runnable {
private static final Logger log = LoggerFactory.getLogger(MessageRunnable.class);
private static final String RESPONSE = "@ResponseUri";
private final Consumer consumer;
private ForkliftMessage msg;
private ClassLoader classLoader;
private Object handler;
private List<Method> onMessage;
private List<Method> onValidate;
private List<Method> onResponse;
private Map<ProcessStep, List<Method>> onProcessStep;
private List<String> errors;
private List<Closeable> closeMe;
private boolean warnOnly = false;
private LifeCycleMonitors lifeCycle;
MessageRunnable(Consumer consumer, ForkliftMessage msg, ClassLoader classLoader, Object handler, List<Method> onMessage,
List<Method> onValidate, List<Method> onResponse, Map<ProcessStep, List<Method>> onProcessStep,
List<Closeable> closeMe) {
this.consumer = consumer;
this.msg = msg;
this.classLoader = classLoader;
if (this.classLoader == null)
this.classLoader = Thread.currentThread().getContextClassLoader();
this.handler = handler;
this.onMessage = onMessage;
this.onValidate = onValidate;
this.onResponse = onResponse;
this.onProcessStep = onProcessStep;
this.errors = new ArrayList<>();
this.closeMe = closeMe;
this.lifeCycle = consumer.getForklift().getLifeCycle();
this.lifeCycle.call(ProcessStep.Pending, this);
}
@Override
public void run() {
RunAsClassLoader.run(classLoader, () -> {
// Always ack message to prevent deadlocks
boolean acknowledged = false;
try {
acknowledged = msg.acknowledge();
} catch (ConnectorException e) {
log.error("Error while acking message.", e);
acknowledged = false;
}
if (!acknowledged) {
close();
return;
}
// { Validating }
runHooks(ProcessStep.Validating);
this.lifeCycle.call(ProcessStep.Validating, this);
for (Method m : onValidate) {
if (m.getReturnType() == List.class) {
addError(runLoggingErrors(() -> (List<String>)m.invoke(handler)));
} else if (m.getReturnType() == boolean.class) {
boolean valid = runLoggingErrors(() -> (boolean)m.invoke(handler));
if (!valid)
addError("Validator " + m.getName() + " returned false");
} else {
addError("onValidate method " + m.getName() + " has wrong return type " + m.getReturnType());
}
}
if (errors.size() > 0) {
// { Invalid }
runHooks(ProcessStep.Invalid);
this.lifeCycle.call(ProcessStep.Invalid, this);
} else {
// { Processing }
runHooks(ProcessStep.Processing);
this.lifeCycle.call(ProcessStep.Processing, this);
for (Method m : onMessage) {
runLoggingErrors(() -> m.invoke(handler));
}
if (errors.size() > 0) {
// { Error }
runHooks(ProcessStep.Error);
this.lifeCycle.call(ProcessStep.Error, this);
} else {
// { Complete }
runHooks(ProcessStep.Complete);
// Handle response decoratored methods.
if (msg.getProperties() != null && msg.getProperties().containsKey(RESPONSE)) {
try {
final URI uri = new URI(msg.getProperties().get(RESPONSE).toString());
onResponse.stream().forEach((m) -> {
runLoggingErrors(() -> {
final Object obj = m.invoke(handler);
final ForkliftMessage respMsg = new ForkliftMessage();
respMsg.setHeaders(msg.getHeaders());
if (m.getReturnType() == String.class)
respMsg.setMsg(obj.toString());
else
respMsg.setMsg(consumer.mapper.writeValueAsString(obj));
switch (uri.getScheme()) {
case "queue":
try (ForkliftProducerI producer = consumer.getForklift().getConnector().getQueueProducer(uri.getHost())) {
System.out.println("Sending: " + respMsg.getMsg());
producer.send(respMsg);
}
break;
case "topic":
try (ForkliftProducerI producer = consumer.getForklift().getConnector().getTopicProducer(uri.getHost())) {
producer.send(respMsg);
}
break;
case "http":
// Fall through to https
case "https":
break;
default:
log.warn("Unable to find mapping for response uri scheme {}", uri.getScheme());
break;
}
return null;
});
});
} catch (Exception e) {
log.error("Unable to determine response uri from {}", msg.getProperties().get(RESPONSE), e);
}
}
this.lifeCycle.call(ProcessStep.Complete, this);
}
}
// Always log all non-null errors
if (this.warnOnly)
getErrors().stream().filter(e -> e != null).forEach(e -> log.warn(e));
else
getErrors().stream().filter(e -> e != null).forEach(e -> log.error(e));
close();
});
}
public void addError(List<String> errors) {
if (errors == null)
return;
this.errors.addAll(errors);
}
public void addError(String e) {
this.errors.add(e);
}
public List<String> getErrors() {
return errors;
}
public ForkliftMessage getMsg() {
return msg;
}
public Object getHandler() {
return handler;
}
public Consumer getConsumer() {
return consumer;
}
/**
* Set logging to warn only. This allows exceptions that would normally be logged as error to be warnings.
* @param b
*/
public void setWarnOnly(boolean b) {
this.warnOnly = b;
}
private void close() {
// Close resources.
try {
for (Closeable c : closeMe)
c.close();
} catch (IOException e) {
log.error("Unable to close a resource", e);
}
}
// This interface and method are for wrapping functions that throw errors, logging and swa
@FunctionalInterface
private interface DangerousSupplier<T> {
T get() throws Throwable;
}
private <T> T runLoggingErrors(DangerousSupplier<T> func) {
try {
return func.get();
} catch (Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw)); // stack trace as a string
if (e.getCause() == null) {
addError(e.getMessage() + '\n' + sw.toString());
} else {
addError(e.getCause().getMessage() + '\n' + sw.toString());
}
return null;
}
}
private void runHooks(ProcessStep step) {
for (Method m : onProcessStep.get(step)) {
runLoggingErrors(() -> m.invoke(handler));
}
}
public static void main(String args[]) throws Exception {
URI uri = new URI("queue://duh");
System.out.println(uri.getScheme());
System.out.println(uri.getHost());
}
}