RmiRequestProcessorImpl.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.processing;

import java.io.Serializable;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;

import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;

import org.rribbit.Request;
import org.rribbit.Response;
import org.rribbit.dispatching.RmiRequestDispatcher;
import org.rribbit.execution.ListenerObjectExecutor;
import org.rribbit.retrieval.ListenerObjectRetriever;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This {@link RequestProcessor} processes requests that it receives from an {@link RmiRequestDispatcher} and returns the result via RMI. In order to use this {@link RequestProcessor},
 * all parameters and return values must implement {@link Serializable}. Note that an {@link RmiRequestProcessorImpl} is not actually an {@link RequestProcessor}, because Java
 * RMI does not allow non-remote methods in a remote interface. This is not a problem though, since an {@link RmiRequestProcessorImpl} receives its requests via RMI and not via a Java
 * interface.
 * <p />
 * Users of this class must call {@link #shutdown()} after use, to clean up the RMI {@link Registry}.
 *
 * @author G.J. Schouten
 *
 */
public class RmiRequestProcessorImpl implements RmiRequestProcessor {

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

	protected LocalRequestProcessor requestProcessor;
	protected Registry registry;

	/**
	 * Sets up a {@link Registry} on the specified portnumber that does NOT use SSL.
	 * <p />
	 * Whenever you use this constructor, be sure to set the {@link ListenerObjectRetriever} AND the {@link ListenerObjectExecutor} with the setters provided by this class.
	 * If you don't, runtime {@link NullPointerException}s will occur.
	 *
	 * @param portnumber			The portnumber to use
	 */
	public RmiRequestProcessorImpl(int portnumber) {
		this(portnumber, null, null);
	}

	/**
	 * Sets up a {@link Registry} on the specified portnumber that does NOT use SSL.
	 * <p />
	 * This constructor is recommended, since it forces you to specify the {@link ListenerObjectRetriever} and {@link ListenerObjectExecutor}. Passing a null value for either
	 * of these will result in a runtime {@link NullPointerException} whenever the {@link RmiRequestProcessorImpl} is used.
	 *
	 * @param portnumber				The portnumber to use
	 * @param listenerObjectRetriever
	 * @param listenerObjectExecutor
	 */
	public RmiRequestProcessorImpl(int portnumber, ListenerObjectRetriever listenerObjectRetriever, ListenerObjectExecutor listenerObjectExecutor) {

		try {
			log.info("Creating RMI Registry");
			registry = LocateRegistry.createRegistry(portnumber);
			Remote stub = UnicastRemoteObject.exportObject(this, 0);
			registry.bind(REGISTRY_KEY, stub);
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
		requestProcessor = new LocalRequestProcessor(listenerObjectRetriever, listenerObjectExecutor);
	}

	/**
	 * Sets up a {@link Registry} on the specified portnumber that uses SSL with the supplied parameters. The following system properties will be set:
	 *
	 * <ul>
	 * 	<li>javax.net.ssl.keyStore</li>
	 * 	<li>javax.net.ssl.keyStorePassword</li>
	 * 	<li>javax.net.ssl.trustStore</li>
	 * </ul>
	 *
	 * Whenever you use this constructor, be sure to set the {@link ListenerObjectRetriever} AND the {@link ListenerObjectExecutor} with the setters provided by this class.
	 * If you don't, runtime {@link NullPointerException}s will occur.
	 *
	 * @param portnumber			The portnumber to use
	 * @param keystoreLocation		The filepath that contains the SSL keystore, without file://
	 * @param keystorePassword		The password of the SSL keystore
	 * @param truststoreLocation	The filepath that contains the SSL truststore, without file://
	 * @param truststorePassword	The password of the SSL truststore
	 */
	public RmiRequestProcessorImpl(int portnumber, String keystoreLocation, String keystorePassword, String truststoreLocation, String truststorePassword) {
		this(portnumber, keystoreLocation, keystorePassword, truststoreLocation, truststorePassword, null, null);
	}

	/**
	 * Sets up a {@link Registry} on the specified portnumber that uses SSL with the supplied parameters. The following system properties will be set:
	 *
	 * <ul>
	 * 	<li>javax.net.ssl.keyStore</li>
	 * 	<li>javax.net.ssl.keyStorePassword</li>
	 * 	<li>javax.net.ssl.trustStore</li>
	 * </ul>
	 *
	 * This constructor is recommended, since it forces you to specify the {@link ListenerObjectRetriever} and {@link ListenerObjectExecutor}. Passing a null value for either
	 * of these will result in a runtime {@link NullPointerException} whenever the {@link RmiRequestProcessorImpl} is used.
	 *
	 * @param portnumber			The portnumber to use
	 * @param keystoreLocation		The filepath that contains the SSL keystore, without file://
	 * @param keystorePassword		The password of the SSL keystore
	 * @param truststoreLocation	The filepath that contains the SSL truststore, without file://
	 * @param truststorePassword	The password of the SSL truststore
	 * @param listenerObjectRetriever
	 * @param listenerObjectExecutor
	 */
	public RmiRequestProcessorImpl(int portnumber, String keystoreLocation, String keystorePassword, String truststoreLocation, String truststorePassword,
		ListenerObjectRetriever listenerObjectRetriever, ListenerObjectExecutor listenerObjectExecutor) {

		try {
			log.info("Setting SSL properties");
			System.setProperty("javax.net.ssl.keyStore", keystoreLocation);
			System.setProperty("javax.net.ssl.keyStorePassword", keystorePassword);
			System.setProperty("javax.net.ssl.trustStore", truststoreLocation);
			System.setProperty("javax.net.ssl.trustStorePassword", truststorePassword);

			log.info("Creating SSL RMI Registry");
			RMIClientSocketFactory csf = new SslRMIClientSocketFactory();
			RMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
			registry = LocateRegistry.createRegistry(portnumber, csf, ssf);
			Remote stub = UnicastRemoteObject.exportObject(this, 0, csf, ssf);
			registry.bind(REGISTRY_KEY, stub);
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
		requestProcessor = new LocalRequestProcessor(listenerObjectRetriever, listenerObjectExecutor);
	}

	/**
	 * Call this method at application shutdown to make sure that the RMI Registry gets closed properly. Pending requests will be allowed to finish before the {@link Registry} is killed.
	 */
	public void shutdown() {

		try {
			log.info("Unbinding this RmiRequestProcessorImpl");
			registry.unbind(REGISTRY_KEY);
			UnicastRemoteObject.unexportObject(this, false);
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public <T> Response<T> processRequestViaRMI(Request request) {

		log.info("Processing RMI Request");
		Response<T> response =  requestProcessor.processRequest(request);
		log.info("Returning Response");
		return response;
	}

	public ListenerObjectRetriever getListenerObjectRetriever() {
		return requestProcessor.getListenerObjectRetriever();
	}

	public void setListenerObjectRetriever(ListenerObjectRetriever listenerObjectRetriever) {
		requestProcessor.setListenerObjectRetriever(listenerObjectRetriever);
	}

	public ListenerObjectExecutor getListenerObjectExecutor() {
		return requestProcessor.getListenerObjectExecutor();
	}

	public void setListenerObjectExecutor(ListenerObjectExecutor listenerObjectExecutor) {
		requestProcessor.setListenerObjectExecutor(listenerObjectExecutor);
	}
}