RmiRequestDispatcher.java
- /*
- * Copyright (C) 2012-2025 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.dispatching;
- import java.rmi.registry.LocateRegistry;
- import java.rmi.registry.Registry;
- import java.rmi.server.RMIClientSocketFactory;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Random;
- import javax.rmi.ssl.SslRMIClientSocketFactory;
- import org.rribbit.Request;
- import org.rribbit.Response;
- import org.rribbit.processing.RmiRequestProcessor;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * This {@link RequestDispatcher} dispatches a {@link Request} to an {@link RmiRequestProcessor} via RMI. It offers automatic loadbalancing and failover.
- * <p />
- * Note that the RMI connection is NOT maintained between requests, meaning that a new connection is set up each time a request is made to this {@link RmiRequestDispatcher}.
- * This also means that, if you choose to use SSL, an SSL handshake is done on each request.
- *
- * @author G.J. Schouten
- *
- */
- public class RmiRequestDispatcher implements RequestDispatcher {
- private static final Logger log = LoggerFactory.getLogger(RmiRequestDispatcher.class);
- protected int retryAttempts = 10;
- protected int portnumber;
- protected String[] hosts;
- protected String truststoreLocation;
- /**
- * Connects to {@link RmiRequestProcessor}s that run on the specified port and on the specified hosts. If multiple hosts are specified, one is randomly chosen at runtime,
- * creating automatic load-balancing. If a connection to a host fails, another one is chosen. The default number of retries is 10 and can be set with the corresponding setter.
- * <p />
- * Use this constructor if you do NOT want to use SSL.
- *
- * @param portnumber The portnumber to use
- * @param hosts The hosts to connect to, you can specify one host multiple times, to give it a proportionally larger chance of being chosen
- */
- public RmiRequestDispatcher(int portnumber, String... hosts) {
- this.portnumber = portnumber;
- this.hosts = hosts;
- }
- /**
- * Connects to {@link RmiRequestProcessor}s that run on the specified port and on the specified hosts. If multiple hosts are specified, one is randomly chosen at runtime,
- * creating automatic load-balancing. If a connection to a host fails, another one is chosen. The default number of retries is 10 and can be set with the corresponding setters.
- * <p />
- * Use this constructor if you DO want to use SSL. The "javax.net.ssl.trustStore" system property will be set.
- *
- * @param truststoreLocation The filepath that contains the SSL truststore, without file://
- * @param portnumber The portnumber to use
- * @param hosts The hosts to connect to, you can specify one host multiple times, to give it a proportionally larger chance of being chosen
- */
- public RmiRequestDispatcher(String truststoreLocation, int portnumber, String... hosts) {
- this.truststoreLocation = truststoreLocation;
- this.portnumber = portnumber;
- this.hosts = hosts;
- System.setProperty("javax.net.ssl.trustStore", truststoreLocation);
- }
- @Override
- public <T> Response<T> dispatchRequest(Request request) {
- List<String> hostsAsList = new ArrayList<>(Arrays.asList(hosts));
- log.info("Connecting to an RmiRequestProcessorImpl");
- for(int i=0; i<retryAttempts; i++) {
- log.info("Attempt: {} (Max Attempts: {})", i+1, retryAttempts);
- //If all the available hosts have been tried, then start over
- if(hostsAsList.isEmpty()) {
- log.info("All hosts have been tried. Re-loading...");
- hostsAsList = new ArrayList<>(Arrays.asList(hosts));
- }
- //Pick a host from the available host and remove it from the list
- int number = new Random().nextInt(hostsAsList.size());
- String host = hostsAsList.remove(number);
- //Connect to the host and dispatch the Request
- try {
- log.info("Connecting to server: '{}:{}'", host, portnumber);
- Registry registry;
- if(truststoreLocation == null) { //No SSL
- registry = LocateRegistry.getRegistry(host, portnumber);
- } else { //SSL
- RMIClientSocketFactory csf = new SslRMIClientSocketFactory();
- registry = LocateRegistry.getRegistry(host, portnumber, csf);
- }
- RmiRequestProcessor rmiRequestProcessor = (RmiRequestProcessor) (registry.lookup(RmiRequestProcessor.REGISTRY_KEY));
- Response<T> response = rmiRequestProcessor.processRequestViaRMI(request);
- log.info("Returning Response");
- return response;
- } catch(Exception e) {
- log.error("Connection failed, trying again...", e);
- }
- }
- log.error("No connection to an RmiRequestProcessorImpl could be made");
- throw new RuntimeException("No connection to an RmiRequestProcessorImpl could be made");
- }
- /**
- * Gets the number of times this {@link RmiRequestDispatcher} retries when it cannot connect to the {@link RmiRequestProcessor}. The default is 10.
- */
- public int getRetryAttempts() {
- return retryAttempts;
- }
- /**
- * Sets the number of times this {@link RmiRequestDispatcher} retries when it cannot connect to the {@link RmiRequestProcessor}. The default is 10.
- *
- * @param retryAttempts
- */
- public void setRetryAttempts(int retryAttempts) {
- this.retryAttempts = retryAttempts;
- }
- }