001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/cluster/StandardCluster.java,v 1.6 2002/06/09 02:19:42 remm Exp $
003: * $Revision: 1.6 $
004: * $Date: 2002/06/09 02:19:42 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 1999 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: * [Additional notices, if required by prior licensing conditions]
061: *
062: */
063:
064: package org.apache.catalina.cluster;
065:
066: import java.beans.PropertyChangeSupport;
067: import java.net.InetAddress;
068: import java.net.MulticastSocket;
069: import java.net.UnknownHostException;
070: import java.io.IOException;
071: import java.util.Vector;
072: import org.apache.catalina.Cluster;
073: import org.apache.catalina.Container;
074: import org.apache.catalina.Lifecycle;
075: import org.apache.catalina.LifecycleEvent;
076: import org.apache.catalina.LifecycleException;
077: import org.apache.catalina.LifecycleListener;
078: import org.apache.catalina.Logger;
079: import org.apache.catalina.util.LifecycleSupport;
080: import org.apache.catalina.util.StringManager;
081:
082: /**
083: * A <b>Cluster</b> implementation. Responsible for setting up
084: * a cluster and provides callers with a valid multicast receiver/sender.
085: *
086: * @author Bip Thelin
087: * @version $Revision: 1.6 $, $Date: 2002/06/09 02:19:42 $
088: */
089:
090: public final class StandardCluster implements Cluster, Lifecycle,
091: Runnable {
092:
093: // ----------------------------------------------------- Instance Variables
094:
095: /**
096: * Descriptive information about this component implementation.
097: */
098: private static final String info = "StandardCluster/1.0";
099:
100: /**
101: * Name to register for the background thread.
102: */
103: private String threadName = "StandardCluster";
104:
105: /**
106: * Name for logging purpose
107: */
108: private String clusterImpName = "StandardCluster";
109:
110: /**
111: * The string manager for this package.
112: */
113: private StringManager sm = StringManager
114: .getManager(Constants.Package);
115:
116: /**
117: * Our Cluster info for this JVM
118: */
119: private ClusterMemberInfo localClusterMember = null;
120:
121: /**
122: * The stack that keeps incoming cluster members
123: */
124: private Vector clusterMembers = new Vector();
125:
126: /**
127: * The background thread.
128: */
129: private Thread thread = null;
130:
131: /**
132: * The background thread completion semaphore.
133: */
134: private boolean threadDone = false;
135:
136: /**
137: * The cluster name to join
138: */
139: private String clusterName = null;
140:
141: /**
142: * The Container associated with this Cluster.
143: */
144: private Container container = null;
145:
146: /**
147: * Our ClusterSender, used when replicating
148: */
149: private ClusterSender clusterSender = null;
150:
151: /**
152: * Our ClusterReceiver
153: */
154: private ClusterReceiver clusterReceiver = null;
155:
156: /**
157: * The MulticastPort to use with this cluster
158: */
159: private int multicastPort;
160:
161: /**
162: * The MulticastAdress to use with this cluster
163: */
164: private InetAddress multicastAddress = null;
165:
166: /**
167: * Our MulticastSocket
168: */
169: private MulticastSocket multicastSocket = null;
170:
171: /**
172: * The lifecycle event support for this component.
173: */
174: private LifecycleSupport lifecycle = new LifecycleSupport(this );
175:
176: /**
177: * Has this component been started?
178: */
179: private boolean started = false;
180:
181: /**
182: * The property change support for this component.
183: */
184: private PropertyChangeSupport support = new PropertyChangeSupport(
185: this );
186:
187: /**
188: * The debug level for this Container
189: */
190: private int debug = 0;
191:
192: /**
193: * The interval for the background thread to sleep
194: */
195: private int checkInterval = 60;
196:
197: // ------------------------------------------------------------- Properties
198:
199: /**
200: * Return descriptive information about this Cluster implementation and
201: * the corresponding version number, in the format
202: * <code><description>/<version></code>.
203: */
204: public String getInfo() {
205: return (this .info);
206: }
207:
208: /**
209: * Return a <code>String</code> containing the name of this
210: * Cluster implementation, used for logging
211: *
212: * @return The Cluster implementation
213: */
214: protected String getName() {
215: return (this .clusterImpName);
216: }
217:
218: /**
219: * Set the debug level for this component
220: *
221: * @param debug The debug level
222: */
223: public void setDebug(int debug) {
224: this .debug = debug;
225: }
226:
227: /**
228: * Get the debug level for this component
229: *
230: * @return The debug level
231: */
232: public int getDebug() {
233: return (this .debug);
234: }
235:
236: /**
237: * Set the name of the cluster to join, if no cluster with
238: * this name is present create one.
239: *
240: * @param clusterName The clustername to join
241: */
242: public void setClusterName(String clusterName) {
243: String oldClusterName = this .clusterName;
244: this .clusterName = clusterName;
245: support.firePropertyChange("clusterName", oldClusterName,
246: this .clusterName);
247: }
248:
249: /**
250: * Return the name of the cluster that this Server is currently
251: * configured to operate within.
252: *
253: * @return The name of the cluster associated with this server
254: */
255: public String getClusterName() {
256: return (this .clusterName);
257: }
258:
259: /**
260: * Set the Container associated with our Cluster
261: *
262: * @param container The Container to use
263: */
264: public void setContainer(Container container) {
265: Container oldContainer = this .container;
266: this .container = container;
267: support.firePropertyChange("container", oldContainer,
268: this .container);
269: }
270:
271: /**
272: * Get the Container associated with our Cluster
273: *
274: * @return The Container associated with our Cluster
275: */
276: public Container getContainer() {
277: return (this .container);
278: }
279:
280: /**
281: * Set the Port associated with our Cluster
282: *
283: * @param port The Port to use
284: */
285: public void setMulticastPort(int multicastPort) {
286: int oldMulticastPort = this .multicastPort;
287: this .multicastPort = multicastPort;
288: support.firePropertyChange("multicastPort", oldMulticastPort,
289: this .multicastPort);
290: }
291:
292: /**
293: * Get the Port associated with our Cluster
294: *
295: * @return The Port associated with our Cluster
296: */
297: public int getMulticastPort() {
298: return (this .multicastPort);
299: }
300:
301: /**
302: * Set the Groupaddress associated with our Cluster
303: *
304: * @param port The Groupaddress to use
305: */
306: public void setMulticastAddress(String multicastAddress) {
307: try {
308: InetAddress oldMulticastAddress = this .multicastAddress;
309: this .multicastAddress = InetAddress
310: .getByName(multicastAddress);
311: support.firePropertyChange("multicastAddress",
312: oldMulticastAddress, this .multicastAddress);
313: } catch (UnknownHostException e) {
314: log(sm.getString("standardCluster.invalidAddress",
315: multicastAddress));
316: }
317: }
318:
319: /**
320: * Get the Groupaddress associated with our Cluster
321: *
322: * @return The Groupaddress associated with our Cluster
323: */
324: public InetAddress getMulticastAddress() {
325: return (this .multicastAddress);
326: }
327:
328: /**
329: * Set the time in seconds for this component to
330: * Sleep before it checks for new received data in the Cluster
331: *
332: * @param checkInterval The time to sleep
333: */
334: public void setCheckInterval(int checkInterval) {
335: int oldCheckInterval = this .checkInterval;
336: this .checkInterval = checkInterval;
337: support.firePropertyChange("checkInterval", oldCheckInterval,
338: this .checkInterval);
339: }
340:
341: /**
342: * Get the time in seconds this Cluster sleeps
343: *
344: * @return The time in seconds this Cluster sleeps
345: */
346: public int getCheckInterval() {
347: return (this .checkInterval);
348: }
349:
350: // --------------------------------------------------------- Public Methods
351:
352: /**
353: * Returns a collection containing <code>ClusterMemberInfo</code>
354: * on the remote members of this Cluster. This method does
355: * not include the local host, to retrieve
356: * <code>ClusterMemberInfo</code> on the local host
357: * use <code>getLocalClusterInfo()</code> instead.
358: *
359: * @return Collection with all members in the Cluster
360: */
361: public ClusterMemberInfo[] getRemoteClusterMembers() {
362: return ((ClusterMemberInfo[]) this .clusterMembers.toArray());
363: }
364:
365: /**
366: * Return cluster information about the local host
367: *
368: * @return Cluster information
369: */
370: public ClusterMemberInfo getLocalClusterMember() {
371: return (this .localClusterMember);
372: }
373:
374: /**
375: * Returns a <code>ClusterSender</code> which is the interface
376: * to use when sending information in the Cluster. senderId is
377: * used as a identifier so that information sent through this
378: * instance can only be used with the respectice
379: * <code>ClusterReceiver</code>
380: *
381: * @return The ClusterSender
382: */
383: public ClusterSender getClusterSender(String senderId) {
384: Logger logger = null;
385: MulticastSender send = new MulticastSender(senderId,
386: multicastSocket, multicastAddress, multicastPort);
387: if (container != null)
388: logger = container.getLogger();
389:
390: send.setLogger(logger);
391: send.setDebug(debug);
392:
393: if (debug > 1)
394: log(sm.getString("standardCluster.createSender", senderId));
395:
396: return (send);
397: }
398:
399: /**
400: * Returns a <code>ClusterReceiver</code> which is the interface
401: * to use when receiving information in the Cluster. senderId is
402: * used as a indentifier, only information send through the
403: * <code>ClusterSender</code> with the same senderId can be received.
404: *
405: * @return The ClusterReceiver
406: */
407: public ClusterReceiver getClusterReceiver(String senderId) {
408: Logger logger = null;
409: MulticastReceiver recv = new MulticastReceiver(senderId,
410: multicastSocket, multicastAddress, multicastPort);
411:
412: if (container != null)
413: logger = container.getLogger();
414:
415: recv.setDebug(debug);
416: recv.setLogger(logger);
417: recv.setCheckInterval(checkInterval);
418: recv.start();
419:
420: if (debug > 1)
421: log(sm
422: .getString("standardCluster.createReceiver",
423: senderId));
424:
425: return (recv);
426: }
427:
428: /**
429: * Log a message on the Logger associated with our Container (if any).
430: *
431: * @param message Message to be logged
432: */
433: protected void log(String message) {
434: Logger logger = null;
435:
436: if (container != null)
437: logger = container.getLogger();
438:
439: if (logger != null) {
440: logger.log(getName() + "[" + container.getName() + "]: "
441: + message);
442: } else {
443: String containerName = null;
444: if (container != null)
445: containerName = container.getName();
446:
447: System.out.println(getName() + "[" + containerName + "]: "
448: + message);
449: }
450: }
451:
452: // --------------------------------------------------------- Private Methods
453:
454: private void processReceive() {
455: Object[] objs = clusterReceiver.getObjects();
456:
457: for (int i = 0; i < objs.length; i++) {
458: clusterMembers.add((ClusterMemberInfo) objs[i]);
459: }
460: }
461:
462: // ------------------------------------------------------ Lifecycle Methods
463:
464: /**
465: * Add a lifecycle event listener to this component.
466: *
467: * @param listener The listener to add
468: */
469: public void addLifecycleListener(LifecycleListener listener) {
470: lifecycle.addLifecycleListener(listener);
471: }
472:
473: /**
474: * Get the lifecycle listeners associated with this lifecycle. If this
475: * Lifecycle has no listeners registered, a zero-length array is returned.
476: */
477: public LifecycleListener[] findLifecycleListeners() {
478:
479: return lifecycle.findLifecycleListeners();
480:
481: }
482:
483: /**
484: * Remove a lifecycle event listener from this component.
485: *
486: * @param listener The listener to remove
487: */
488: public void removeLifecycleListener(LifecycleListener listener) {
489: lifecycle.removeLifecycleListener(listener);
490: }
491:
492: /**
493: * Prepare for the beginning of active use of the public methods of this
494: * component. This method should be called after <code>configure()</code>,
495: * and before any of the public methods of the component are utilized.
496: *
497: * @exception LifecycleException if this component detects a fatal error
498: * that prevents this component from being used
499: */
500: public void start() throws LifecycleException {
501: // Validate and update our current component state
502: if (started)
503: throw new LifecycleException(sm
504: .getString("standardCluster.alreadyStarted"));
505:
506: try {
507: multicastSocket = new MulticastSocket(multicastPort);
508:
509: if (multicastSocket != null && multicastAddress != null) {
510: multicastSocket.joinGroup(multicastAddress);
511:
512: clusterSender = getClusterSender(getName());
513: clusterReceiver = getClusterReceiver(getName());
514:
515: localClusterMember = new ClusterMemberInfo();
516: localClusterMember.setClusterName(getClusterName());
517: localClusterMember.setHostName(null);
518: localClusterMember.setClusterInfo(getInfo());
519:
520: clusterSender.send(localClusterMember);
521:
522: if (debug > 1)
523: log(sm.getString("standardCluster.joinGroup",
524: multicastAddress));
525: } else {
526: log(sm.getString("standardCluster.socketOrAddressNull"));
527: }
528: } catch (IOException e) {
529: log(sm.getString("standardCluster.joinException", e
530: .toString()));
531: }
532:
533: lifecycle.fireLifecycleEvent(START_EVENT, null);
534: started = true;
535:
536: // Start the background reaper thread
537: threadStart();
538: }
539:
540: /**
541: * Gracefully terminate the active use of the public methods of this
542: * component. This method should be the last one called on a given
543: * instance of this component.
544: *
545: * @exception LifecycleException if this component detects a fatal error
546: * that needs to be reported
547: */
548: public void stop() throws LifecycleException {
549: // Validate and update our current component state
550: if (!started)
551: log(sm.getString("standardCluster.notStarted"));
552:
553: try {
554: multicastSocket.leaveGroup(multicastAddress);
555: multicastSocket = null;
556: } catch (IOException e) {
557: log(sm.getString("standardCluster.leaveException",
558: multicastAddress));
559: }
560:
561: if (debug > 1)
562: log(sm.getString("standardCluster.leaveGroup",
563: multicastAddress));
564:
565: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
566: started = false;
567:
568: // Stop the background reaper thread
569: threadStop();
570: }
571:
572: // ------------------------------------------------------ Background Thread
573:
574: /**
575: * The background thread.
576: */
577: public void run() {
578: // Loop until the termination semaphore is set
579: while (!threadDone) {
580: processReceive();
581: threadSleep();
582: }
583: }
584:
585: /**
586: * Sleep for the duration specified by the <code>checkInterval</code>
587: * property.
588: */
589: private void threadSleep() {
590: try {
591: Thread.sleep(checkInterval * 1000L);
592: } catch (InterruptedException e) {
593: ;
594: }
595: }
596:
597: /**
598: * Start the background thread.
599: */
600: private void threadStart() {
601: if (thread != null)
602: return;
603:
604: threadDone = false;
605: threadName = "StandardCluster[" + getClusterName() + "]";
606: thread = new Thread(this , threadName);
607: thread.setDaemon(true);
608: thread.start();
609: }
610:
611: /**
612: * Stop the background thread.
613: */
614: private void threadStop() {
615: if (thread == null)
616: return;
617:
618: threadDone = true;
619: thread.interrupt();
620: try {
621: thread.join();
622: } catch (InterruptedException e) {
623: ;
624: }
625:
626: thread = null;
627: }
628: }
|