org.atmosphere.plugin.rmi.RMIPeerManager Maven / Gradle / Ivy
/*
* 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
*
* http://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.atmosphere.plugin.rmi;
import org.atmosphere.cpr.AtmosphereConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
/**
*
* This manager is in charge of sending all broadcast messages to a set of known peers (thanks to its
* {@link RMIPeerManager#sendAll(String, Object)} method) and to bind a {@link RMIBroadcastService} (thanks to
* its {@link RMIPeerManager#server(String, RMIBroadcastService, AtmosphereConfig) method}).
*
*
*
* The manager is a singleton. To be instantiated successfully the first time its {@link RMIPeerManager#getInstance()}
* method is called, a property file at {@link RMIPeerManager#RMI_PROPERTIES_LOCATION this} location must exists
* in the classpath. Both RMI server port and peers location must be declared. The port is associated to the key
* {@link RMIPeerManager#RMI_SERVER_PORT_PROPERTY} and each peer location will be associated to a key starting by
* {@link RMIPeerManager#PEER_PROPERTY_PREFIX this} prefix. Sample of properties file content :
*
* rmi.server.port=4000
* rmi.peer.server1=com.my.company.server1:4000
* rmi.peer.server2=com.my.company.server2:4000
*
*
*
*
* NOTE : the properties file should not contains the peer's URL of the server this manager is running on.
*
*
*
* Moreover, any value declared in the properties file could be overridden with a system property. Each system property
* must starts with {@link RMIPeerManager#SYSTEM_PROPERTY_PREFIX this} prefix followed by the property key as declared in
* the properties file. For instance, if I want to override the value of the property 'rmi.peer.server1' declared in the
* properties file, I will run my server with '-D org.atmosphere.rmi.peer.server1=my.overridden.host:port' in the command
* line.
*
*
*
* TODO : Should be enhanced to discover peers automatically thanks to multicast
*
*
* @author Guillaume DROUET
* @version 1.0
* @since 1.1.1
*/
public class RMIPeerManager {
/**
* Singleton.
*/
private static RMIPeerManager instance;
/**
* Expected path for the properties file defining all the peers.
*/
private static final String RMI_PROPERTIES_LOCATION = "/org/atmosphere/plugin/rmi/rmi.properties";
/**
* Required server port.
*/
private static final String RMI_SERVER_PORT_PROPERTY = "rmi.server.port";
/**
* Required prefix for all keys referencing a peer's URL in the properties file.
*/
private static final String PEER_PROPERTY_PREFIX = "rmi.peer.";
/**
* Required prefix for properties to be overridden through system properties.
*/
private static final String SYSTEM_PROPERTY_PREFIX = "org.atmosphere.";
/**
* Server port system property.
*/
private static final String RMI_SERVER_PORT_SYSTEM_PROPERTY = SYSTEM_PROPERTY_PREFIX + RMI_SERVER_PORT_PROPERTY;
/**
* Prefix for all system properties referencing a peer's URL.
*/
private static final String PEER_SYSTEM_PROPERTY_PREFIX = SYSTEM_PROPERTY_PREFIX + PEER_PROPERTY_PREFIX;
/**
* Logger.
*/
private final Logger logger = LoggerFactory.getLogger(RMIPeerManager.class);
/**
* All the discovered peers.
*/
private List peers;
/**
* Registry to use when creating the server.
*/
private Registry registry;
/**
* The port to use when creating the registry.
*/
private int serverPort;
/**
*
* Builds a new instance by loading properties from {@link RMIPeerManager#RMI_PROPERTIES_LOCATION}.
*
*
*
* If the properties file is not found in the classpath or could not be read successfully,
* then an {@code IllegalStateException} will be thrown.
*
*
*
* If one property does not refer to a valid URL, then an {@code IllegalArgumentException} will be thrown.
*
*/
private RMIPeerManager() {
final Properties properties = new Properties();
logger.info("Looking for '{}' file in the classpath", RMI_PROPERTIES_LOCATION);
final InputStream peerProperties = getClass().getResourceAsStream(RMI_PROPERTIES_LOCATION);
if (peerProperties != null) {
try {
logger.info("Loading '{}' file from classpath", RMI_PROPERTIES_LOCATION);
properties.load(peerProperties);
} catch (final IOException ioe) {
logger.error("Unable to load '" + RMI_PROPERTIES_LOCATION + "' file from the classpath", ioe);
}
}
peers = new ArrayList();
discoverServerPort(properties);
discoverPeers(properties);
}
/**
*
* Discovers the port to bind when creating the RMI server in the given {@code Properties} object.
*
*
*
* If the given port is not a valid integer, then an {@code IllegalArgumentException} will be thrown.
*
*
* @param properties the properties containing the RMI port.
*/
private void discoverServerPort(final Properties properties) {
String portValue = properties.getProperty(RMI_SERVER_PORT_PROPERTY);
// Looking for system property
final String sysPropertyValue = System.getProperty(RMI_SERVER_PORT_SYSTEM_PROPERTY);
if (sysPropertyValue != null) {
logger.info("System property '{}' set. Overriding value '{}' with '{}'",
new Object[] { RMI_SERVER_PORT_SYSTEM_PROPERTY, portValue, sysPropertyValue, });
portValue = sysPropertyValue;
}
if (portValue == null) {
throw new IllegalArgumentException(RMI_SERVER_PORT_PROPERTY + " property's value is null. Must be a valid integer");
}
try {
serverPort = Integer.parseInt(portValue);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(RMI_SERVER_PORT_PROPERTY + " property's value is not an integer : " + portValue, nfe);
}
}
/**
*
* Discovers all the peer name to authority (host:port) mappings defined in the given {@code Properties} object and/or the system properties.
*
*
* If the same peer name occurs in both the given {@code Properties} object and the system properties,
* the system properties authority overrides the given {@code Properties} authority.
*
* @param properties Properties containing peer name to authority mappings
*
* @throws IllegalArgumentException If any peer authority (host:port) is malformed
*/
private void discoverPeers(final Properties properties) {
logger.info("Discovering RMI peers");
final Map sysPropPeerAuthorityByPeerName = new HashMap();
// Add peers from system properties
for (final Entry