All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.atmosphere.plugin.rmi.RMIPeerManager Maven / Gradle / Ivy

There is a newer version: 2.6.5
Show newest version
/*
 * 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 sysProp : System.getProperties().entrySet()) { final String sysPropKey = sysProp.getKey().toString(); if (sysPropKey.startsWith(PEER_SYSTEM_PROPERTY_PREFIX)) { final String peerName = sysPropKey.substring(PEER_SYSTEM_PROPERTY_PREFIX.length()); final String peerAuthority = sysProp.getValue().toString(); addPeer(peerName, peerAuthority); sysPropPeerAuthorityByPeerName.put(peerName, peerAuthority); logger.info("Added peer '{}' with authority '{}' from system properties", peerName, peerAuthority); } } // Add peers from properties that weren't overridden by system properties for (final Entry property : properties.entrySet()) { final String propertyKey = property.getKey().toString(); if (propertyKey.startsWith(PEER_PROPERTY_PREFIX)) { final String peerName = propertyKey.substring(PEER_PROPERTY_PREFIX.length()); final String peerAuthority = property.getValue().toString(); final String sysPropPeerAuthority = sysPropPeerAuthorityByPeerName.get(peerName); if (sysPropPeerAuthority == null) { addPeer(peerName, peerAuthority); logger.info("Added peer '{}' with authority '{}' from properties", peerName, peerAuthority); } else if (! sysPropPeerAuthority.equals(peerAuthority)) { logger.info( "Peer '{}' with authority '{}' from system properties overrode authority '{}'", new Object[] {peerName, sysPropPeerAuthority, peerAuthority} ); } } } } private void addPeer(final String peerName, final String peerAuthority) { try { peers.add(new Peer("rmi://" + peerAuthority + '/' + RMIBroadcastService.class.getSimpleName() + '/')); } catch (final MalformedURLException mue) { throw new IllegalArgumentException( "Value for peer '" + peerName + "' must be a valid host name and port (e.g., foo:40001). Invalid value: " + peerAuthority ); } } /** *

* Gets the unique instance of {@link RMIPeerManager}. *

* *

* If this is the first time the method is called, the singleton will be instantiated here. *

* * @return the unique instance */ public synchronized static RMIPeerManager getInstance() { if (instance == null) { instance = new RMIPeerManager(); } return instance; } /** *

* Sends the given message to the broadcaster identified by the given ID belonging to all the registered peers. *

* * @param broadcasterId the broadcaster ID * @param message the message to be sent */ public synchronized void sendAll(final String broadcasterId, final Object message) { logger.info("Sending message to {} known RMI peers", peers.size()); for (Peer peer : peers) { peer.send(broadcasterId, message, 1); } } /** *

* Creates a service by binding the given service for the given broadcaster ID. *

* @param broadcasterId the broadcaster ID * @param service the service to be bound * @param config the atmosphere config */ public synchronized void server(final String broadcasterId, final RMIBroadcastService service, AtmosphereConfig config) { try { if (registry == null) { logger.info("Creating registry with port {}", serverPort); registry = LocateRegistry.createRegistry(serverPort); if (config != null) { config.shutdownHook(new AtmosphereConfig.ShutdownHook() { @Override public void shutdown() { for (Thread t : Thread.getAllStackTraces().keySet()) { if ("RMI Reaper".equals(t.getName())) { t.interrupt(); } } } }); } } logger.info("Rebinding {}", RMIBroadcastService.class.getSimpleName()); final String url = RMIBroadcastService.class.getSimpleName() + "/" + broadcasterId; logger.info("URL : {}", url); registry.rebind(url, service); } catch (RemoteException re) { logger.error("Unable to create the RMI server. Won't receive message to broadcast from other peers", re); } } /** *

* Internal class which represents a peer handling connections where messages should be broadcast. *

* *

* Ths class encapsulates a {@link RMIBroadcastService} retrieved remotely to send the messages. If the * connection could not be established because the service is not already reachable, the messages are lost. * When a new message is sent, the {@link Peer} tries to reconnect and if it succeeds, it sends it. *

* */ private class Peer { /** * The remote URL. */ String url; /** *

* Creates a new instance. If the connection could not be established because of a non reachable remote * service, the object will try to reconnect when a message will be sent. *

* * @param peerUrl the remote service URL * @throws MalformedURLException if the URL is not correct */ Peer(final String peerUrl) throws MalformedURLException { logger.info("Connecting to peer at {}", peerUrl); url = peerUrl; // Just connect to detect a MalformedURLException exception connect(""); } /** *

* Tries to establish a connection by looking up the remote service dedicated to the specified broadcaster ID. *

* * @param broadcasterId the broadcaster ID * @return the remote interface, {@code } * @throws MalformedURLException if the {@link Peer#url} is not correct */ RMIBroadcastService connect(final String broadcasterId) throws MalformedURLException { try { logger.info("Trying to connect to {}", url); return (RMIBroadcastService) Naming.lookup(url + broadcasterId); } catch (RemoteException re) { logger.warn("Could not reach the remote host with the url {}. Reason is '{}'. Will try later", new Object[] { url, re.getMessage() }, re); } catch (NotBoundException nbe) { logger.warn("{} for url {} not currently bound. Reason is {}. Will try later", new Object[] { RMIBroadcastService.class.getSimpleName(), url, nbe.getMessage() }, nbe); } return null; } /** *

* Sends the given message to the broadcaster identified by the specified ID. *

* *

* Its first tries to connect. Once the connection is established, the given message is sent. * If the connection fails or if an error occurs, the method will retries a specified number of times. *

* * @param broadcasterId the broadcaster ID * @param message the message to send * @param retry how many times the method will retry if an error occurs */ synchronized void send(final String broadcasterId, final Object message, final int retry) { try { RMIBroadcastService service; if ((service = connect(broadcasterId)) != null) { logger.debug("Sending message '{}' to peer at url {}", new Object[] { message, url, }); if (retry > 0) { try { service.send(message); } catch(Exception e) { logger.warn("Send operation failed {}. Retrying...", e.getMessage()); send(broadcasterId, message, retry - 1); } } else { service.send(message); } } } catch (MalformedURLException mue) { // Should never occurs since the URL has been validated when the class was instantiated throw new IllegalStateException(mue); } catch (RemoteException re) { logger.warn("Failed to send message to peer '{}'", url, re); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy