AbstractListenerObjectExecutor.java
/*
* Copyright (C) 2012-2024 RRiBbit.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rribbit.execution;
import org.rribbit.ListenerObject;
import org.rribbit.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
/**
* This {@link ListenerObjectExecutor} provides a blueprint for the execution of a {@link Collection} of {@link ListenerObject}s and the processing of their results. The
* only thing left to implementation subclasses is to actually use the methods provided in this class to actually do the execution. The implementations can decide on, for example, sequential
* or multi-threaded execution of the different {@link ListenerObject}s.
* <p />
* Please note that this class uses the java method {@link Method}.invoke() and this method does NOT work with varargs. So, if you want to call a listener method that accepts varargs, you MUST
* explicitly pass an array where the varargs are expected, even if that array is empty, otherwise, the method will NOT match and will NOT be executed.
*
* @author G.J. Schouten
*
*/
public abstract class AbstractListenerObjectExecutor implements ListenerObjectExecutor {
private static final Logger log = LoggerFactory.getLogger(AbstractListenerObjectExecutor.class);
@Override
public <T> Response<T> executeListeners(Collection<ListenerObject> listenerObjects, Object... parameters) {
Collection<T> results = new ArrayList<>();
Collection<Throwable> throwables = new ArrayList<>();
log.debug("Executing ListenerObjects");
for(ExecutionResult executionResult : this.doExecuteListeners(listenerObjects, parameters)) {
if(executionResult != null && !(executionResult instanceof VoidResult)) {
if(executionResult instanceof ThrowableResult) {
throwables.add(((ThrowableResult) executionResult).getThrowable());
} else {
results.add((T) (((ObjectResult) executionResult).getResult()));
}
}
}
return new Response<>(results, throwables);
}
/**
* This method executes a single {@link ListenerObject} and returns the appropriate {@link ExecutionResult}. It should be used by subclasses when excuting the {@link ListenerObject}s.
*
* @param listenerObject
* @param parameters
* @return the {@link ExecutionResult} of the execution of the {@link ListenerObject}, or null if the {@link ListenerObject} did not match the parameters
*/
protected ExecutionResult executeSingleListenerObject(ListenerObject listenerObject, Object... parameters) {
try {
Object returnValue = listenerObject.getMethod().invoke(listenerObject.getTarget(), parameters);
if(listenerObject.getMethod().getReturnType().equals(void.class)) { //Nothing to return
log.debug("ListenerObject '{}' successfully executed, return value was void", listenerObject);
return new VoidResult();
} else {
log.debug("ListenerObject '{}' successfully executed, return value was object", listenerObject);
return new ObjectResult(returnValue);
}
} catch(InvocationTargetException e) {
log.debug("Underlying method of ListenerObject '{}' threw Throwable", listenerObject);
//Caused by the underlying method throwing a Throwable. Rethrowing...
return new ThrowableResult(e.getCause());
} catch(Exception e) {
//Note: In Java 18, the JVM sometimes throws an InvocationTargetException instead of an IllegalArgumentException
//if the parameters don't match the Method signature, thereby ending up in the above catch clause instead of this
//one. This seems to be fixed in Java 21, but if it occurs again, the solution is to return null from the
//above catch clause if the cause of the InvocationTargetException is actually mismatching parameters, in order
//to match the handling of that scenario in this catch clause.
log.trace("Method of ListenerObject '" + listenerObject + "' did not match parameters, ignoring", e);
//Probably caused by parameters not matching Method signature. Ignoring...
return null;
}
}
/**
* This method should call {@link #executeSingleListenerObject(ListenerObject, Object...)} on each {@link ListenerObject}, accumulate the results, and return. Typical implementations
* do this either sequentially, or multi-threaded.
*
* @param listenerObjects
* @param parameters
* @return a {@link Collection} with the {@link ExecutionResult}s of the given {@link ListenerObject}s or an empty {@link Collection} if there are no results, never returns null
*/
protected abstract Collection<ExecutionResult> doExecuteListeners(Collection<ListenerObject> listenerObjects, Object... parameters);
/**
* This class represents the outcome of the execution of a {@link ListenerObject}.
*
* @author G.J. Schouten
*
*/
protected abstract static class ExecutionResult {}
/**
* This {@link ExecutionResult} represents a successful invocation, but no result (method return type was 'void').
*
* @author G.J. Schouten
*
*/
protected static class VoidResult extends ExecutionResult {}
/**
* This {@link ExecutionResult} represents an unsuccessful invocation, where the method threw a {@link Throwable}.
*
* @author G.J. Schouten
*
*/
protected static class ThrowableResult extends ExecutionResult {
private Throwable throwable;
public ThrowableResult(Throwable throwable) {
this.throwable = throwable;
}
public Throwable getThrowable() {
return throwable;
}
}
/**
* This {@link ExecutionResult} represents a successful invocation, along with its result.
*
* @author G.J. Schouten
*/
protected static class ObjectResult extends ExecutionResult {
private Object result;
public ObjectResult(Object result) {
this.result = result;
}
public Object getResult() {
return result;
}
}
}