View Javadoc

1   /**
2    *  Copyright 2003-2006 Greg Luck
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    *      http://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  
17  package net.sf.ehcache.distribution;
18  
19  import net.sf.ehcache.Cache;
20  import net.sf.ehcache.CacheException;
21  import net.sf.ehcache.CacheManager;
22  import net.sf.ehcache.event.CacheEventListener;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.net.InetAddress;
27  import java.net.UnknownHostException;
28  import java.rmi.Naming;
29  import java.rmi.Remote;
30  import java.rmi.RemoteException;
31  import java.rmi.registry.LocateRegistry;
32  import java.rmi.registry.Registry;
33  import java.rmi.server.ExportException;
34  import java.rmi.server.UnicastRemoteObject;
35  import java.util.ArrayList;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Set;
39  
40  /**
41   * A cache server which exposes available cache operations remotely through RMI.
42   * <p/>
43   * It acts as a Decorator to a Cache. It holds an instance of cache, which is a local cache it talks to.
44   * <p/>
45   * This class could specify a security manager with code like:
46   * <pre>
47   * if (System.getSecurityManager() == null) {
48   *     System.setSecurityManager(new RMISecurityManager());
49   * }
50   * </pre>
51   * Doing so would require the addition of <code>grant</code> statements in the <code>java.policy</code> file.
52   * <p/>
53   * Per the JDK documentation: "If no security manager is specified no class loading, by RMI clients or servers, is allowed,
54   * aside from what can be found in the local CLASSPATH." The classpath of each instance of this class should have
55   * all required classes to enable distribution, so no remote classloading is required or desirable. Accordingly,
56   * no security manager is set and there are no special JVM configuration requirements.
57   * <p/>
58   *
59   * @author Greg Luck
60   * @version $Id: RMICacheManagerPeerListener.java 52 2006-04-24 14:50:03Z gregluck $
61   */
62  public final class RMICacheManagerPeerListener implements CacheManagerPeerListener {
63  
64      private static final Log LOG = LogFactory.getLog(RMICacheManagerPeerListener.class.getName());
65      private static final int MINIMUM_SENSIBLE_TIMEOUT = 200;
66  
67      private Registry registry;
68  
69      private final String hostName;
70      private Integer port;
71      private CacheManager cacheManager;
72      private Integer socketTimeoutMillis;
73  
74      private final List cachePeers = new ArrayList();
75  
76      /**
77       * Constructor with full arguments.
78       *
79       * @param hostName            may be null, in which case the hostName will be looked up. Machines with multiple
80       *                            interfaces should specify this if they do not want it to be the default NIC.
81       * @param port                a port in the range 1025 - 65536
82       * @param cacheManager        the CacheManager this listener belongs to
83       * @param socketTimeoutMillis TCP/IP Socket timeout when waiting on response
84       */
85      public RMICacheManagerPeerListener(String hostName, Integer port, CacheManager cacheManager,
86                                         Integer socketTimeoutMillis) throws UnknownHostException {
87          if (hostName != null && hostName.length() != 0) {
88              this.hostName = hostName;
89              if (hostName.equals("localhost")) {
90                  LOG.warn("Explicitly setting the listener hostname to 'localhost' is not recommended. "
91                  + "It will only work if all CacheManager peers are on the same machine.");
92              }
93          } else {
94              this.hostName = calculateHostAddress();
95          }
96          if (port == null) {
97              throw new IllegalArgumentException("port must be specified in the range 1025 - 65536");
98          } else {
99              this.port = port;
100         }
101         this.cacheManager = cacheManager;
102         if (socketTimeoutMillis == null || socketTimeoutMillis.intValue() < MINIMUM_SENSIBLE_TIMEOUT) {
103             throw new IllegalArgumentException("socketTimoutMillis must be a reasonable value greater than 200ms");
104         }
105         this.socketTimeoutMillis = socketTimeoutMillis;
106 
107     }
108 
109 
110     private static String calculateHostAddress() throws UnknownHostException {
111         return InetAddress.getLocalHost().getHostAddress();
112     }
113 
114 
115     /**
116      * {@inheritDoc}
117      */
118     public final void init() throws CacheException {
119         RMICachePeer rmiCachePeer = null;
120         try {
121             startRegistry();
122             populateListOfRemoteCachePeers();
123             for (int i = 0; i < cachePeers.size(); i++) {
124                 rmiCachePeer = (RMICachePeer) cachePeers.get(i);
125                 Naming.rebind(rmiCachePeer.getUrl(), rmiCachePeer);
126             }
127             LOG.debug("Server bound in registry");
128         } catch (Exception e) {
129             String url = null;
130             if (rmiCachePeer != null) {
131                 url = rmiCachePeer.getUrl();
132             }
133 
134             throw new CacheException("Problem starting listener for RMICachePeer "
135                     + url + ". Initial cause was " + e.getMessage(), e);
136         }
137     }
138 
139     /**
140      * Returns a list of bound objects.
141      * <p/>
142      * This should match the list of cachePeers i.e. they should always be bound
143      * @return a list of String representations of <code>RMICachePeer</code> objects
144      */
145     final String[] listBoundRMICachePeers() throws CacheException {
146         try {
147             return registry.list();
148         } catch (RemoteException e) {
149             throw new CacheException("Unable to list cache peers " + e.getMessage());
150         }
151     }
152 
153     /**
154      * Returns a reference to the remote object.
155      * @param name the name of the cache e.g. <code>sampleCache1</code>
156      */
157     final Remote lookupPeer(String name) throws CacheException {
158         try {
159             return registry.lookup(name);
160         } catch (Exception e) {
161             throw new CacheException("Unable to lookup peer for replicated cache " + name + " "
162                     + e.getMessage());
163         }
164     }
165 
166     /**
167      * Should be called on init because this is one of the last things that should happen on CacheManager startup.
168      */
169     private void populateListOfRemoteCachePeers() throws RemoteException {
170         String[] names = cacheManager.getCacheNames();
171         for (int i = 0; i < names.length; i++) {
172             String name = names[i];
173             Cache cache = cacheManager.getCache(name);
174             if (isDistributed(cache)) {
175                 RMICachePeer peer = new RMICachePeer(cache, hostName, port, socketTimeoutMillis);
176                 cachePeers.add(peer);
177             }
178         }
179 
180     }
181 
182     /**
183      * Determine if the given cache is distributed.
184      * @param cache the cache to check
185      * @return true if a <code>CacheReplicator</code> is found in the listeners
186      */
187     private static boolean isDistributed(Cache cache) {
188         Set listeners = cache.getCacheEventNotificationService().getCacheEventListeners();
189         for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
190             CacheEventListener cacheEventListener = (CacheEventListener) iterator.next();
191             if (cacheEventListener instanceof CacheReplicator) {
192                 return true;
193             }
194         }
195         return false;
196     }
197 
198     /**
199      * Start the rmiregistry.
200      * <p/>
201      * The alternative is to use the <code>rmiregistry</code> binary, in which case:
202      * <ol/>
203      * <li>rmiregistry running
204      * <li>-Djava.rmi.server.codebase="file:///Users/gluck/work/ehcache/build/classes/ file:///Users/gluck/work/ehcache/lib/commons-logging-1.0.4.jar"
205      * </ol>
206      * There appears to be no way to stop an rmiregistry. We check to see if one if already "there"
207      * before we create a new one.
208      *
209      * @throws RemoteException
210      */
211     private void startRegistry() throws RemoteException {
212         try {
213             registry = LocateRegistry.getRegistry(port.intValue());
214             try {
215                 registry.list();
216             } catch (RemoteException e) {
217                 //may not be created. Let's create it.
218                 registry = LocateRegistry.createRegistry(port.intValue());
219             }
220         } catch (ExportException exception) {
221             LOG.fatal("Exception starting RMI registry. Error was " + exception.getMessage(), exception);
222         }
223     }
224 
225     /**
226      * Stop the listener. It
227      * <ul>
228      * <li>unexports Remote objects
229      * <li>unbinds the objects from the registry
230      * </ul>
231      */
232     public final void dispose() throws CacheException {
233         try {
234             for (int i = 0; i < cachePeers.size(); i++) {
235                 RMICachePeer rmiCachePeer = (RMICachePeer) cachePeers.get(i);
236                 UnicastRemoteObject.unexportObject(rmiCachePeer, false);
237                 Naming.unbind(rmiCachePeer.getUrl());
238             }
239             LOG.debug("Server unbound in registry");
240         } catch (Exception e) {
241             throw new CacheException("Problem unbinding remote cache peers. Initial cause was " + e.getMessage(), e);
242         }
243     }
244 
245     /**
246      * All of the caches which are listenting for remote changes.
247      *
248      * @return a list of <code>RMICachePeer</code> objects
249      */
250     public final List getBoundCachePeers() {
251         return cachePeers;
252     }
253 
254     /**
255      * Gets a list of cache peers
256      */
257     final List getCachePeers() {
258         return cachePeers;
259     }
260 }