DefaultRequestResponseBus.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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.rribbit.dispatching.RequestDispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of {@link RequestResponseBus}. Provides all behaviour specified in that interface.
* <p />
* 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
* performance considerations in mind when adding multiple {@link RequestDispatcher}s to a {@link DefaultRequestResponseBus}, especially when using RRiBbit Remoting. In that case,
* every request will be sent to ALL remote machines and the {@link DefaultRequestResponseBus} will have to wait until all of them have returned.
* <p />
* 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
* 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
* the sender does not have to know anything about the receiver), it could be a worthwile sacrifice for performance considerations.
*
* @author G.J. Schouten
*
*/
public class DefaultRequestResponseBus implements RequestResponseBus {
private static final Logger log = LoggerFactory.getLogger(DefaultRequestResponseBus.class);
protected List<RequestDispatcher> requestDispatchers;
/**
* Whenever you use this constructor, be sure to set the {@link RequestDispatcher} with the setter provided by this class.
* If you don't, runtime {@link NullPointerException}s will occur.
*/
public DefaultRequestResponseBus() {
requestDispatchers = new CopyOnWriteArrayList<>();
}
/**
* This constructor is recommended, since it forces you to specify a {@link RequestDispatcher}. Passing a null value will result in a
* runtime {@link NullPointerException} whenever the {@link DefaultRequestResponseBus} is used.
*
* @param requestDispatchers The {@link RequestDispatcher}s to be used by this {@link DefaultRequestResponseBus}
*/
public DefaultRequestResponseBus(RequestDispatcher... requestDispatchers) {
this();
this.requestDispatchers.addAll(Arrays.asList(requestDispatchers));
}
@Override
public <T> T sendForSingleOfClass(Class<T> returnType, Object... parameters) {
log.info("Sending request for single object of class: '{}'", returnType.getName());
log.debug("Creating Request object");
Request request = new Request(returnType, null, parameters);
log.debug("Dispatching Request");
List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
return returnValues.isEmpty() ? null : returnValues.get(0);
}
@Override
public <T> Collection<T> sendForMultipleOfClass(Class<T> returnType, Object... parameters) {
log.info("Sending request for multiple objects of class: '{}'", returnType.getName());
log.debug("Creating Request object");
Request request = new Request(returnType, null, parameters);
log.debug("Dispatching Request");
return this.dispatchRequestAndProcessResponse(request);
}
@Override
public void sendForNothing(Object... parameters) {
log.info("Sending request for nothing");
log.debug("Creating Request object");
Request request = new Request(null, null, parameters);
log.debug("Dispatching Request");
this.dispatchRequestAndProcessResponse(request);
}
@Override
public <T> T sendForSingleWithHint(String hint, Object... parameters) {
log.info("Sending request for single object with hint '{}'", hint);
log.debug("Creating Request object");
Request request = new Request(null, hint, parameters);
log.debug("Dispatching Request");
List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
return returnValues.isEmpty() ? null : returnValues.get(0);
}
@Override
public <T> Collection<T> sendForMultipleWithHint(String hint, Object... parameters) {
log.info("Sending request for multiple objects with hint '{}'", hint);
log.debug("Creating Request object");
Request request = new Request(null, hint, parameters);
log.debug("Dispatching Request");
return this.dispatchRequestAndProcessResponse(request);
}
@Override
public void sendForNothingWithHint(String hint, Object... parameters) {
log.info("Sending request for nothing with hint '{}'", hint);
log.debug("Creating Request object");
Request request = new Request(null, hint, parameters);
log.debug("Dispatching Request");
this.dispatchRequestAndProcessResponse(request);
}
@Override
public <T> T sendForSingleOfClassWithHint(Class<T> returnType, String hint, Object... parameters) {
log.info("Sending request for single object of class '{}' and with hint '{}'", returnType.getName(), hint);
log.debug("Creating Request object");
Request request = new Request(returnType, hint, parameters);
log.debug("Dispatching Request");
List<T> returnValues = this.dispatchRequestAndProcessResponse(request);
return returnValues.isEmpty() ? null : returnValues.get(0);
}
@Override
public <T> Collection<T> sendForMultipleOfClassWithHint(Class<T> returnType, String hint, Object... parameters) {
log.info("Sending request for multiple objects of class '{}' and with hint '{}'", returnType.getName(), hint);
log.debug("Creating Request object");
Request request = new Request(returnType, hint, parameters);
log.debug("Dispatching Request");
return this.dispatchRequestAndProcessResponse(request);
}
@Override
public <T> T send(String hint, Object... parameters) {
return this.sendForSingleWithHint(hint, parameters);
}
/**
* 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
* an {@link Exception} is thrown, otherwise, the aggregated return values are returned.
*
* @param request The {@link Request} to be dispatched to the {@link RequestDispatcher}s
* @return All return values returned by all {@link RequestDispatcher}s
*/
protected <T> List<T> dispatchRequestAndProcessResponse(Request request) {
//Dispatch Request to all RequestDispatchers and aggregate results
List<T> returnValues = new ArrayList<>();
Collection<Throwable> throwables = new ArrayList<>();
for(RequestDispatcher requestDispatcher : requestDispatchers) {
Response<T> response = requestDispatcher.dispatchRequest(request);
returnValues.addAll(response.getReturnValues());
throwables.addAll(response.getThrowables());
}
//Process Throwables
log.debug("Processing Responses");
if(throwables.size() == 1) {
this.throwAny(throwables.iterator().next());
} else if(!throwables.isEmpty()) {
throw new MultipleThrowablesOccurredException(throwables);
}
//Return returnValues
return returnValues;
}
/**
* 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.
* A cast to {@link RuntimeException} is not included by the compiler.
*
* @param e
* @throws E
*/
private <E extends Throwable> void throwAny(Throwable e) throws E {
throw (E) e;
}
/**
* Returns all {@link RequestDispatcher}s that are used by this {@link DefaultRequestResponseBus}.
*
* @return All {@link RequestDispatcher}s that are used by this {@link DefaultRequestResponseBus}
*/
public List<RequestDispatcher> getRequestDispatchers() {
return requestDispatchers;
}
/**
* Adds a {@link RequestDispatcher} to this {@link DefaultRequestResponseBus}.
*
* @param requestDispatcher The {@link RequestDispatcher} that needs to be added to this {@link DefaultRequestResponseBus}
*/
public void addRequestDispatcher(RequestDispatcher requestDispatcher) {
requestDispatchers.add(requestDispatcher);
}
/**
* Removes all {@link RequestDispatcher}s from this {@link DefaultRequestResponseBus} and adds only the given one. After invoking this method, only the given
* {@link RequestDispatcher} will be used by this {@link DefaultRequestResponseBus}.
*
* @param requestDispatcher The {@link RequestDispatcher} that must be the sole {@link RequestDispatcher} used by this {@link DefaultRequestResponseBus}
*/
public void setRequestDispatcher(RequestDispatcher requestDispatcher) {
requestDispatchers.clear();
requestDispatchers.add(requestDispatcher);
}
}