RmiRequestDispatcher.java

  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.dispatching;

  17. import java.rmi.registry.LocateRegistry;
  18. import java.rmi.registry.Registry;
  19. import java.rmi.server.RMIClientSocketFactory;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.List;
  23. import java.util.Random;

  24. import javax.rmi.ssl.SslRMIClientSocketFactory;

  25. import org.rribbit.Request;
  26. import org.rribbit.Response;
  27. import org.rribbit.processing.RmiRequestProcessor;
  28. import org.slf4j.Logger;
  29. import org.slf4j.LoggerFactory;

  30. /**
  31.  * This {@link RequestDispatcher} dispatches a {@link Request} to an {@link RmiRequestProcessor} via RMI. It offers automatic loadbalancing and failover.
  32.  * <p />
  33.  * 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}.
  34.  * This also means that, if you choose to use SSL, an SSL handshake is done on each request.
  35.  *
  36.  * @author G.J. Schouten
  37.  *
  38.  */
  39. public class RmiRequestDispatcher implements RequestDispatcher {

  40.     private static final Logger log = LoggerFactory.getLogger(RmiRequestDispatcher.class);

  41.     protected int retryAttempts = 10;
  42.     protected int portnumber;
  43.     protected String[] hosts;
  44.     protected String truststoreLocation;

  45.     /**
  46.      * 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,
  47.      * 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.
  48.      * <p />
  49.      * Use this constructor if you do NOT want to use SSL.
  50.      *
  51.      * @param portnumber            The portnumber to use
  52.      * @param hosts                 The hosts to connect to, you can specify one host multiple times, to give it a proportionally larger chance of being chosen
  53.      */
  54.     public RmiRequestDispatcher(int portnumber, String... hosts) {

  55.         this.portnumber = portnumber;
  56.         this.hosts = hosts;
  57.     }

  58.     /**
  59.      * 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,
  60.      * 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.
  61.      * <p />
  62.      * Use this constructor if you DO want to use SSL. The "javax.net.ssl.trustStore" system property will be set.
  63.      *
  64.      * @param truststoreLocation    The filepath that contains the SSL truststore, without file://
  65.      * @param portnumber            The portnumber to use
  66.      * @param hosts                 The hosts to connect to, you can specify one host multiple times, to give it a proportionally larger chance of being chosen
  67.      */
  68.     public RmiRequestDispatcher(String truststoreLocation, int portnumber, String... hosts) {

  69.         this.truststoreLocation = truststoreLocation;
  70.         this.portnumber = portnumber;
  71.         this.hosts = hosts;

  72.         System.setProperty("javax.net.ssl.trustStore", truststoreLocation);
  73.     }

  74.     @Override
  75.     public <T> Response<T> dispatchRequest(Request request) {

  76.         List<String> hostsAsList = new ArrayList<>(Arrays.asList(hosts));

  77.         log.info("Connecting to an RmiRequestProcessorImpl");
  78.         for(int i=0; i<retryAttempts; i++) {
  79.             log.info("Attempt: {} (Max Attempts: {})", i+1, retryAttempts);

  80.             //If all the available hosts have been tried, then start over
  81.             if(hostsAsList.isEmpty()) {
  82.                 log.info("All hosts have been tried. Re-loading...");
  83.                 hostsAsList = new ArrayList<>(Arrays.asList(hosts));
  84.             }

  85.             //Pick a host from the available host and remove it from the list
  86.             int number = new Random().nextInt(hostsAsList.size());
  87.             String host = hostsAsList.remove(number);

  88.             //Connect to the host and dispatch the Request
  89.             try {
  90.                 log.info("Connecting to server: '{}:{}'", host, portnumber);
  91.                 Registry registry;
  92.                 if(truststoreLocation == null) { //No SSL
  93.                     registry = LocateRegistry.getRegistry(host, portnumber);
  94.                 } else { //SSL
  95.                     RMIClientSocketFactory csf = new SslRMIClientSocketFactory();
  96.                     registry = LocateRegistry.getRegistry(host, portnumber, csf);
  97.                 }

  98.                 RmiRequestProcessor rmiRequestProcessor = (RmiRequestProcessor) (registry.lookup(RmiRequestProcessor.REGISTRY_KEY));
  99.                 Response<T> response = rmiRequestProcessor.processRequestViaRMI(request);
  100.                 log.info("Returning Response");
  101.                 return response;
  102.             } catch(Exception e) {
  103.                 log.error("Connection failed, trying again...", e);
  104.             }
  105.         }
  106.         log.error("No connection to an RmiRequestProcessorImpl could be made");
  107.         throw new RuntimeException("No connection to an RmiRequestProcessorImpl could be made");
  108.     }

  109.     /**
  110.      * Gets the number of times this {@link RmiRequestDispatcher} retries when it cannot connect to the {@link RmiRequestProcessor}. The default is 10.
  111.      */
  112.     public int getRetryAttempts() {
  113.         return retryAttempts;
  114.     }

  115.     /**
  116.      * Sets the number of times this {@link RmiRequestDispatcher} retries when it cannot connect to the {@link RmiRequestProcessor}. The default is 10.
  117.      *
  118.      * @param retryAttempts
  119.      */
  120.     public void setRetryAttempts(int retryAttempts) {
  121.         this.retryAttempts = retryAttempts;
  122.     }
  123. }