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
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 }