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);
	}
}