1 /*
2 * Copyright (C) 2012-2025 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 }