View Javadoc
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;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.List;
22  import java.util.concurrent.CopyOnWriteArrayList;
23  
24  import org.rribbit.dispatching.RequestDispatcher;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  /**
29   * Default implementation of {@link RequestResponseBus}. Provides all behaviour specified in that interface.
30   * <p />
31   * The {@link DefaultRequestResponseBus} supports multiple {@link RequestDispatcher}s and every request will be sent to ALL of them. The responses will then be aggregated. Please keep
32   * performance considerations in mind when adding multiple {@link RequestDispatcher}s to a {@link DefaultRequestResponseBus}, especially when using RRiBbit Remoting. In that case,
33   * every request will be sent to ALL remote machines and the {@link DefaultRequestResponseBus} will have to wait until all of them have returned.
34   * <p />
35   * If this is undesirable, an alternative is to create multiple {@link DefaultRequestResponseBus}es, each with its own {@link RequestDispatcher}. This does however, require the sender
36   * of the request to have knowledge about where the receiver is located (i.e. which {@link RequestResponseBus} to call). Although this contradicts the principle behind event buses (in that
37   * the sender does not have to know anything about the receiver), it could be a worthwile sacrifice for performance considerations.
38   *
39   * @author G.J. Schouten
40   *
41   */
42  public class DefaultRequestResponseBus implements RequestResponseBus {
43  
44  	private static final Logger log = LoggerFactory.getLogger(DefaultRequestResponseBus.class);
45  
46  	protected List<RequestDispatcher> requestDispatchers;
47  
48  	/**
49  	 * Whenever you use this constructor, be sure to set the {@link RequestDispatcher} with the setter provided by this class.
50  	 * If you don't, runtime {@link NullPointerException}s will occur.
51  	 */
52  	public DefaultRequestResponseBus() {
53  		requestDispatchers = new CopyOnWriteArrayList<>();
54  	}
55  
56  	/**
57  	 * This constructor is recommended, since it forces you to specify a {@link RequestDispatcher}. Passing a null value will result in a
58  	 * runtime {@link NullPointerException} whenever the {@link DefaultRequestResponseBus} is used.
59  	 *
60  	 * @param requestDispatchers The {@link RequestDispatcher}s to be used by this {@link DefaultRequestResponseBus}
61  	 */
62  	public DefaultRequestResponseBus(RequestDispatcher... requestDispatchers) {
63  		this();
64  		this.requestDispatchers.addAll(Arrays.asList(requestDispatchers));
65  	}
66  
67  	@Override
68  	public <T> T sendForSingleOfClass(Class<T> returnType, Object... parameters) {
69  
70  		log.info("Sending request for single object of class: '{}'", returnType.getName());
71  
72  		log.debug("Creating Request object");
73  		Request request = new Request(returnType, null, parameters);
74  
75  		log.debug("Dispatching Request");
76  		List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
77  		return returnValues.isEmpty() ? null : returnValues.get(0);
78  	}
79  
80  	@Override
81  	public <T> Collection<T> sendForMultipleOfClass(Class<T> returnType, Object... parameters) {
82  
83  		log.info("Sending request for multiple objects of class: '{}'", returnType.getName());
84  
85  		log.debug("Creating Request object");
86  		Request request = new Request(returnType, null, parameters);
87  
88  		log.debug("Dispatching Request");
89  		return this.dispatchRequestAndProcessResponse(request);
90  	}
91  
92  	@Override
93  	public void sendForNothing(Object... parameters) {
94  
95  		log.info("Sending request for nothing");
96  
97  		log.debug("Creating Request object");
98  		Request request = new Request(null, null, parameters);
99  
100 		log.debug("Dispatching Request");
101 		this.dispatchRequestAndProcessResponse(request);
102 	}
103 
104 	@Override
105 	public <T> T sendForSingleWithHint(String hint, Object... parameters) {
106 
107 		log.info("Sending request for single object with hint '{}'", hint);
108 
109 		log.debug("Creating Request object");
110 		Request request = new Request(null, hint, parameters);
111 
112 		log.debug("Dispatching Request");
113 		List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
114 		return returnValues.isEmpty() ? null : returnValues.get(0);
115 	}
116 
117 	@Override
118 	public <T> Collection<T> sendForMultipleWithHint(String hint, Object... parameters) {
119 
120 		log.info("Sending request for multiple objects with hint '{}'", hint);
121 
122 		log.debug("Creating Request object");
123 		Request request = new Request(null, hint, parameters);
124 
125 		log.debug("Dispatching Request");
126 		return this.dispatchRequestAndProcessResponse(request);
127 	}
128 
129 	@Override
130 	public void sendForNothingWithHint(String hint, Object... parameters) {
131 
132 		log.info("Sending request for nothing with hint '{}'", hint);
133 
134 		log.debug("Creating Request object");
135 		Request request = new Request(null, hint, parameters);
136 
137 		log.debug("Dispatching Request");
138 		this.dispatchRequestAndProcessResponse(request);
139 	}
140 
141 	@Override
142 	public <T> T sendForSingleOfClassWithHint(Class<T> returnType, String hint, Object... parameters) {
143 
144 		log.info("Sending request for single object of class '{}' and with hint '{}'", returnType.getName(), hint);
145 
146 		log.debug("Creating Request object");
147 		Request request = new Request(returnType, hint, parameters);
148 
149 		log.debug("Dispatching Request");
150 		List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
151 		return returnValues.isEmpty() ? null : returnValues.get(0);
152 	}
153 
154 	@Override
155 	public <T> Collection<T> sendForMultipleOfClassWithHint(Class<T> returnType, String hint, Object... parameters) {
156 
157 		log.info("Sending request for multiple objects of class '{}' and with hint '{}'", returnType.getName(), hint);
158 
159 		log.debug("Creating Request object");
160 		Request request = new Request(returnType, hint, parameters);
161 
162 		log.debug("Dispatching Request");
163 		return this.dispatchRequestAndProcessResponse(request);
164 	}
165 
166 	@Override
167 	public <T> T send(String hint, Object... parameters) {
168 
169 		return this.sendForSingleWithHint(hint, parameters);
170 	}
171 
172 	/**
173 	 * Sends the given {@link Request} to all available {@link RequestDispatcher}s and aggregates the {@link Response}s. If there are any {@link Throwable}s, then
174 	 * an {@link Exception} is thrown, otherwise, the aggregated return values are returned.
175 	 *
176 	 * @param request	The {@link Request} to be dispatched to the {@link RequestDispatcher}s
177 	 * @return			All return values returned by all {@link RequestDispatcher}s
178 	 */
179 	protected <T> List<T> dispatchRequestAndProcessResponse(Request request) {
180 
181 		//Dispatch Request to all RequestDispatchers and aggregate results
182 		List<T> returnValues = new ArrayList<>();
183 		Collection<Throwable> throwables = new ArrayList<>();
184 		for(RequestDispatcher requestDispatcher : requestDispatchers) {
185 			Response<T> response = requestDispatcher.dispatchRequest(request);
186 			returnValues.addAll(response.getReturnValues());
187 			throwables.addAll(response.getThrowables());
188 		}
189 
190 		//Process Throwables
191 		log.debug("Processing Responses");
192 		if(throwables.size() == 1) {
193 			this.throwAny(throwables.iterator().next());
194 		} else if(!throwables.isEmpty()) {
195 			throw new MultipleThrowablesOccurredException(throwables);
196 		}
197 
198 		//Return returnValues
199 		return returnValues;
200 	}
201 
202 	/**
203 	 * Nice little trick to throw checked {@link Throwable}s without declaring them. It works, because generics are erased at runtime and the bound for E is Throwable.
204 	 * A cast to {@link RuntimeException} is not included by the compiler.
205 	 *
206 	 * @param e
207 	 * @throws E
208 	 */
209 	private <E extends Throwable> void throwAny(Throwable e) throws E {
210 		throw (E) e;
211 	}
212 
213 	/**
214 	 * Returns all {@link RequestDispatcher}s that are used by this {@link DefaultRequestResponseBus}.
215 	 *
216 	 * @return  All {@link RequestDispatcher}s that are used by this {@link DefaultRequestResponseBus}
217 	 */
218 	public List<RequestDispatcher> getRequestDispatchers() {
219 		return requestDispatchers;
220 	}
221 
222 	/**
223 	 * Adds a {@link RequestDispatcher} to this {@link DefaultRequestResponseBus}.
224 	 *
225 	 * @param requestDispatcher The {@link RequestDispatcher} that needs to be added to this {@link DefaultRequestResponseBus}
226 	 */
227 	public void addRequestDispatcher(RequestDispatcher requestDispatcher) {
228 		requestDispatchers.add(requestDispatcher);
229 	}
230 
231 	/**
232 	 * Removes all {@link RequestDispatcher}s from this {@link DefaultRequestResponseBus} and adds only the given one. After invoking this method, only the given
233 	 * {@link RequestDispatcher} will be used by this {@link DefaultRequestResponseBus}.
234 	 *
235 	 * @param requestDispatcher The {@link RequestDispatcher} that must be the sole {@link RequestDispatcher} used by this {@link DefaultRequestResponseBus}
236 	 */
237 	public void setRequestDispatcher(RequestDispatcher requestDispatcher) {
238 		requestDispatchers.clear();
239 		requestDispatchers.add(requestDispatcher);
240 	}
241 }