1 /* 2 * Copyright (C) 2012-2024 RRiBbit.org 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.rribbit.execution; 17 18 import org.rribbit.ListenerObject; 19 import org.rribbit.Response; 20 import org.slf4j.Logger; 21 import org.slf4j.LoggerFactory; 22 23 import java.lang.reflect.InvocationTargetException; 24 import java.lang.reflect.Method; 25 import java.util.ArrayList; 26 import java.util.Collection; 27 28 /** 29 * This {@link ListenerObjectExecutor} provides a blueprint for the execution of a {@link Collection} of {@link ListenerObject}s and the processing of their results. The 30 * 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 31 * or multi-threaded execution of the different {@link ListenerObject}s. 32 * <p /> 33 * 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 34 * 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. 35 * 36 * @author G.J. Schouten 37 * 38 */ 39 public abstract class AbstractListenerObjectExecutor implements ListenerObjectExecutor { 40 41 private static final Logger log = LoggerFactory.getLogger(AbstractListenerObjectExecutor.class); 42 43 @Override 44 public <T> Response<T> executeListeners(Collection<ListenerObject> listenerObjects, Object... parameters) { 45 46 Collection<T> results = new ArrayList<>(); 47 Collection<Throwable> throwables = new ArrayList<>(); 48 49 log.debug("Executing ListenerObjects"); 50 for(ExecutionResult executionResult : this.doExecuteListeners(listenerObjects, parameters)) { 51 if(executionResult != null && !(executionResult instanceof VoidResult)) { 52 if(executionResult instanceof ThrowableResult) { 53 throwables.add(((ThrowableResult) executionResult).getThrowable()); 54 } else { 55 results.add((T) (((ObjectResult) executionResult).getResult())); 56 } 57 } 58 } 59 return new Response<>(results, throwables); 60 } 61 62 /** 63 * 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. 64 * 65 * @param listenerObject 66 * @param parameters 67 * @return the {@link ExecutionResult} of the execution of the {@link ListenerObject}, or null if the {@link ListenerObject} did not match the parameters 68 */ 69 protected ExecutionResult executeSingleListenerObject(ListenerObject listenerObject, Object... parameters) { 70 71 try { 72 Object returnValue = listenerObject.getMethod().invoke(listenerObject.getTarget(), parameters); 73 if(listenerObject.getMethod().getReturnType().equals(void.class)) { //Nothing to return 74 log.debug("ListenerObject '{}' successfully executed, return value was void", listenerObject); 75 return new VoidResult(); 76 } else { 77 log.debug("ListenerObject '{}' successfully executed, return value was object", listenerObject); 78 return new ObjectResult(returnValue); 79 } 80 } catch(InvocationTargetException e) { 81 log.debug("Underlying method of ListenerObject '{}' threw Throwable", listenerObject); 82 //Caused by the underlying method throwing a Throwable. Rethrowing... 83 return new ThrowableResult(e.getCause()); 84 } catch(Exception e) { 85 //Note: In Java 18, the JVM sometimes throws an InvocationTargetException instead of an IllegalArgumentException 86 //if the parameters don't match the Method signature, thereby ending up in the above catch clause instead of this 87 //one. This seems to be fixed in Java 21, but if it occurs again, the solution is to return null from the 88 //above catch clause if the cause of the InvocationTargetException is actually mismatching parameters, in order 89 //to match the handling of that scenario in this catch clause. 90 91 log.trace("Method of ListenerObject '" + listenerObject + "' did not match parameters, ignoring", e); 92 //Probably caused by parameters not matching Method signature. Ignoring... 93 return null; 94 } 95 } 96 97 /** 98 * This method should call {@link #executeSingleListenerObject(ListenerObject, Object...)} on each {@link ListenerObject}, accumulate the results, and return. Typical implementations 99 * do this either sequentially, or multi-threaded. 100 * 101 * @param listenerObjects 102 * @param parameters 103 * @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 104 */ 105 protected abstract Collection<ExecutionResult> doExecuteListeners(Collection<ListenerObject> listenerObjects, Object... parameters); 106 107 /** 108 * This class represents the outcome of the execution of a {@link ListenerObject}. 109 * 110 * @author G.J. Schouten 111 * 112 */ 113 protected abstract static class ExecutionResult {} 114 115 /** 116 * This {@link ExecutionResult} represents a successful invocation, but no result (method return type was 'void'). 117 * 118 * @author G.J. Schouten 119 * 120 */ 121 protected static class VoidResult extends ExecutionResult {} 122 123 /** 124 * This {@link ExecutionResult} represents an unsuccessful invocation, where the method threw a {@link Throwable}. 125 * 126 * @author G.J. Schouten 127 * 128 */ 129 protected static class ThrowableResult extends ExecutionResult { 130 131 private Throwable throwable; 132 133 public ThrowableResult(Throwable throwable) { 134 this.throwable = throwable; 135 } 136 137 public Throwable getThrowable() { 138 return throwable; 139 } 140 } 141 142 /** 143 * This {@link ExecutionResult} represents a successful invocation, along with its result. 144 * 145 * @author G.J. Schouten 146 */ 147 protected static class ObjectResult extends ExecutionResult { 148 149 private Object result; 150 151 public ObjectResult(Object result) { 152 this.result = result; 153 } 154 155 public Object getResult() { 156 return result; 157 } 158 } 159 }