RmiRequestDispatcher.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.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;
}
}