001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.admin;
006:
007: import com.tc.config.schema.L2Info;
008: import com.tc.management.JMXConnectorProxy;
009:
010: import java.io.IOException;
011: import java.rmi.ConnectException;
012: import java.util.EventListener;
013: import java.util.HashMap;
014: import java.util.Map;
015: import java.util.Timer;
016: import java.util.TimerTask;
017: import java.util.logging.Level;
018: import java.util.logging.Logger;
019:
020: import javax.management.AttributeChangeNotification;
021: import javax.management.Notification;
022: import javax.management.NotificationListener;
023: import javax.management.ObjectName;
024: import javax.management.remote.JMXConnector;
025: import javax.management.remote.JMXServiceURL;
026:
027: public class ServerConnectionManager implements NotificationListener {
028: private L2Info m_l2Info;
029: private boolean m_autoConnect;
030: private ConnectionContext m_connectCntx;
031: private ConnectionListener m_connectListener;
032: private JMXConnectorProxy m_jmxConnector;
033: private HashMap<String, Object> m_connectEnv;
034: private ServerHelper m_serverHelper;
035: private boolean m_connected;
036: private boolean m_started;
037: private boolean m_active;
038: private boolean m_passiveUninitialized;
039: private boolean m_passiveStandby;
040: private Exception m_connectException;
041: private ConnectThread m_connectThread;
042: private ConnectionMonitorAction m_connectMonitorAction;
043: private Timer m_connectMonitorTimer;
044: private AutoConnectListener m_autoConnectListener;
045:
046: private static final Map<String, String[]> m_credentialsMap = new HashMap<String, String[]>();
047:
048: private static final int CONNECT_MONITOR_PERIOD = 1000;
049:
050: private static final Object m_connectTestLock = new Object();
051:
052: static {
053: String levelName = System
054: .getProperty("ServerConnectionManager.logLevel");
055: Level level = Level.OFF;
056:
057: if (levelName != null) {
058: try {
059: level = Level.parse(levelName);
060: } catch (IllegalArgumentException ie) {
061: level = Level.ALL;
062: }
063: }
064: Logger.getLogger("javax.management.remote").setLevel(level);
065: }
066:
067: public ServerConnectionManager(String host, int port,
068: boolean autoConnect, ConnectionListener listener) {
069: this (new L2Info(host, host, port), autoConnect, listener);
070: }
071:
072: public ServerConnectionManager(L2Info l2Info, boolean autoConnect,
073: ConnectionListener listener) {
074: m_autoConnect = autoConnect;
075: m_connectListener = listener;
076: m_serverHelper = ServerHelper.getHelper();
077:
078: setL2Info(l2Info);
079: }
080:
081: public L2Info getL2Info() {
082: return m_l2Info;
083: }
084:
085: public void setL2Info(L2Info l2Info) {
086: cancelActiveServices();
087:
088: m_l2Info = l2Info;
089: m_connectCntx = new ConnectionContext(l2Info);
090:
091: try {
092: if (isAutoConnect()) {
093: startConnect();
094: }
095: } catch (Exception e) {/**/
096: }
097: }
098:
099: public void setHostname(String hostname) {
100: setL2Info(new L2Info(m_l2Info.name(), hostname, m_l2Info
101: .jmxPort()));
102: }
103:
104: public void setJMXPortNumber(int port) {
105: setL2Info(new L2Info(m_l2Info.name(), m_l2Info.host(), port));
106: }
107:
108: public void setCredentials(String username, String password) {
109: Map<String, Object> connEnv = getConnectionEnvironment();
110: connEnv.put("jmx.remote.credentials", new String[] { username,
111: password });
112: }
113:
114: public String[] getCredentials() {
115: Map<String, Object> connEnv = getConnectionEnvironment();
116: return (String[]) connEnv.get("jmx.remote.credentials");
117: }
118:
119: static void cacheCredentials(ServerConnectionManager scm,
120: String[] credentials) {
121: m_credentialsMap.put(scm.toString(), credentials);
122: m_credentialsMap.put(scm.getHostname(), credentials);
123: }
124:
125: public static String[] getCachedCredentials(
126: ServerConnectionManager scm) {
127: String[] result = m_credentialsMap.get(scm.toString());
128: if (result == null) {
129: result = m_credentialsMap.get(scm.getHostname());
130: }
131: return result;
132: }
133:
134: public void setAutoConnect(boolean autoConnect) {
135: if (m_autoConnect != autoConnect) {
136: if ((m_autoConnect = autoConnect) == true) {
137: if (!m_connected) {
138: startConnect();
139: }
140: } else {
141: cancelActiveServices();
142: }
143: }
144: }
145:
146: public boolean isAutoConnect() {
147: return m_autoConnect;
148: }
149:
150: public void setConnectionContext(ConnectionContext cc) {
151: m_connectCntx = cc;
152: }
153:
154: public ConnectionContext getConnectionContext() {
155: return m_connectCntx;
156: }
157:
158: public void setJMXConnector(JMXConnector jmxc) throws IOException {
159: m_connectException = null;
160: m_connectCntx.jmxc = jmxc;
161: m_connectCntx.mbsc = jmxc.getMBeanServerConnection();
162: setConnected(true);
163: }
164:
165: protected void setConnected(boolean connected) {
166: if (m_connected != connected) {
167: synchronized (this ) {
168: m_connected = connected;
169: if (m_connected == false) {
170: cancelActiveServices();
171: m_active = m_started = m_passiveUninitialized = m_passiveStandby = false;
172: if (isAutoConnect()) {
173: startConnect();
174: }
175: } else { // connected
176: cacheCredentials(ServerConnectionManager.this ,
177: getCredentials());
178: m_started = true;
179: if ((m_active = internalIsActive()) == false) {
180: if ((m_passiveUninitialized = internalIsPassiveUninitialized()) == false) {
181: m_passiveStandby = internalIsPassiveStandby();
182: }
183: addActivationListener();
184: }
185: initConnectionMonitor();
186: }
187: }
188:
189: // Notify listener that the connection state changed.
190: if (m_connectListener != null) {
191: m_connectListener.handleConnection();
192: }
193: }
194: }
195:
196: /**
197: * Mark not-connected, notify, cancel connection monitor, don't startup auto-connect thread.
198: */
199: void disconnectOnExit() {
200: cancelActiveServices();
201: if (m_connected) {
202: m_connected = false;
203: if (m_connectListener != null) {
204: m_connectListener.handleConnection();
205: }
206: }
207: }
208:
209: /**
210: * Since we have all of this infrastructure, turn off the JMXRemote connection monitoring stuff.
211: */
212: public Map<String, Object> getConnectionEnvironment() {
213: if (m_connectEnv == null) {
214: m_connectEnv = new HashMap<String, Object>();
215: m_connectEnv.put(
216: "jmx.remote.x.client.connection.check.period",
217: new Long(0));
218: m_connectEnv.put("jmx.remote.default.class.loader",
219: getClass().getClassLoader());
220: }
221: return m_connectEnv;
222: }
223:
224: class ConnectorCloser implements Runnable {
225: private JMXConnector m_connector;
226:
227: ConnectorCloser(JMXConnector connector) {
228: m_connector = connector;
229: }
230:
231: public void run() {
232: try {
233: m_connector.close();
234: } catch (Exception e) {/**/
235: }
236: }
237: }
238:
239: private void initConnector() {
240: if (m_jmxConnector != null)
241: new Thread(new ConnectorCloser(m_jmxConnector)).start();
242: m_jmxConnector = new JMXConnectorProxy(getHostname(),
243: getJMXPortNumber(), getConnectionEnvironment());
244: }
245:
246: public JMXConnector getJmxConnector() {
247: initConnector();
248: return m_jmxConnector;
249: }
250:
251: private void startConnect() {
252: try {
253: cancelConnectThread();
254: initConnector();
255: m_connectThread = new ConnectThread();
256: m_connectThread.start();
257: } catch (Exception e) {
258: m_connectException = e;
259: if (m_connectListener != null) {
260: m_connectListener.handleException();
261: }
262: }
263: }
264:
265: private void cancelConnectThread() {
266: if (m_connectThread != null && m_connectThread.isAlive()) {
267: try {
268: m_connectThread.cancel();
269: m_connectThread = null;
270: } catch (Exception ignore) {/**/
271: }
272: }
273: }
274:
275: static boolean isConnectException(IOException ioe) {
276: Throwable t = ioe;
277:
278: while (t != null) {
279: if (t instanceof ConnectException) {
280: return true;
281: }
282: t = t.getCause();
283: }
284:
285: return false;
286: }
287:
288: public boolean testIsConnected() throws Exception {
289: if (m_connectCntx == null)
290: return false;
291:
292: synchronized (m_connectTestLock) {
293: if (m_connectCntx == null)
294: return false;
295:
296: if (m_connectCntx.jmxc == null) {
297: initConnector();
298: }
299:
300: m_jmxConnector.connect(getConnectionEnvironment());
301: m_connectCntx.mbsc = m_jmxConnector
302: .getMBeanServerConnection();
303: m_connectCntx.jmxc = m_jmxConnector;
304: m_connectException = null;
305:
306: return true;
307: }
308: }
309:
310: class ConnectThread extends Thread {
311: private boolean m_cancel = false;
312:
313: ConnectThread() {
314: super ();
315: setPriority(MIN_PRIORITY);
316: }
317:
318: public void run() {
319: try {
320: sleep(500);
321: } catch (InterruptedException ie) {/**/
322: }
323:
324: while (!m_cancel && !m_connected) {
325: try {
326: boolean isConnected = testIsConnected();
327: if (!m_cancel) {
328: setConnected(isConnected);
329: }
330: return;
331: } catch (Exception e) {
332: if (m_cancel) {
333: return;
334: } else {
335: m_connectException = e;
336: if (m_connectListener != null) {
337: if (e instanceof SecurityException) {
338: setAutoConnect(false);
339: fireToggleAutoConnectEvent();
340: m_connectListener.handleException();
341: return;
342: }
343: m_connectListener.handleException();
344: }
345: }
346: }
347:
348: try {
349: sleep(2000);
350: } catch (InterruptedException ie) {
351: // We may interrupt the connect thread when a new host or port comes in
352: // because we have to recreate the connection context, JMX service URL,
353: // and connect thread.
354: return;
355: }
356: }
357: }
358:
359: void cancel() {
360: m_cancel = true;
361: }
362: }
363:
364: void addToggleAutoConnectListener(AutoConnectListener listener) {
365: m_autoConnectListener = listener;
366: }
367:
368: private void fireToggleAutoConnectEvent() {
369: if (m_autoConnectListener != null)
370: m_autoConnectListener.handleEvent();
371: }
372:
373: JMXServiceURL getJMXServiceURL() {
374: return m_jmxConnector.getServiceURL();
375: }
376:
377: public String getName() {
378: return m_l2Info.name();
379: }
380:
381: public String getHostname() {
382: return m_l2Info.host();
383: }
384:
385: public int getJMXPortNumber() {
386: return m_l2Info.jmxPort();
387: }
388:
389: public boolean isConnected() {
390: return m_connected;
391: }
392:
393: public Exception getConnectionException() {
394: return m_connectException;
395: }
396:
397: public boolean isActive() {
398: return m_active;
399: }
400:
401: public boolean canShutdown() {
402: try {
403: return m_serverHelper.canShutdown(m_connectCntx);
404: } catch (Exception e) {
405: return false;
406: }
407: }
408:
409: private boolean internalIsActive() {
410: try {
411: return m_serverHelper.isActive(m_connectCntx);
412: } catch (Exception e) {
413: return false;
414: }
415: }
416:
417: public boolean isStarted() {
418: return m_started;
419: }
420:
421: private boolean internalIsPassiveUninitialized() {
422: try {
423: return m_serverHelper.isPassiveUninitialized(m_connectCntx);
424: } catch (Exception e) {
425: return false;
426: }
427: }
428:
429: public boolean isPassiveUninitialized() {
430: return m_passiveUninitialized;
431: }
432:
433: private boolean internalIsPassiveStandby() {
434: try {
435: return m_serverHelper.isPassiveStandby(m_connectCntx);
436: } catch (Exception e) {
437: return false;
438: }
439: }
440:
441: public boolean isPassiveStandby() {
442: return m_passiveStandby;
443: }
444:
445: void initConnectionMonitor() {
446: if (m_connectMonitorAction == null) {
447: m_connectMonitorAction = new ConnectionMonitorAction();
448: }
449: if (m_connectMonitorTimer == null) {
450: m_connectMonitorTimer = new Timer();
451: m_connectMonitorTimer.schedule(m_connectMonitorAction,
452: CONNECT_MONITOR_PERIOD, CONNECT_MONITOR_PERIOD);
453: }
454: }
455:
456: private class ConnectionMonitorAction extends TimerTask {
457: public void run() {
458: if (m_connectCntx.isConnected()) {
459: try {
460: m_connectCntx.testConnection();
461: } catch (Exception e) {
462: cancelConnectionMonitor();
463: setConnected(false);
464: }
465: }
466: }
467: }
468:
469: void cancelConnectionMonitor() {
470: if (m_connectMonitorTimer != null) {
471: m_connectMonitorTimer.cancel();
472: m_connectMonitorAction.cancel();
473: m_connectMonitorAction = null;
474: m_connectMonitorTimer = null;
475: }
476: }
477:
478: /**
479: * Register for a JMX callback when the server transitions from started->...->active. We do this when we notice that
480: * the server is started but not yet active.
481: */
482: void addActivationListener() {
483: try {
484: ObjectName infoMBean = m_serverHelper
485: .getServerInfoMBean(m_connectCntx);
486: m_connectCntx.addNotificationListener(infoMBean, this );
487: if ((m_active = internalIsActive()) == true) {
488: m_connectCntx.removeNotificationListener(infoMBean,
489: this );
490: }
491: } catch (Exception e) {/**/
492: }
493: }
494:
495: void removeActivationListener() {
496: try {
497: ObjectName infoMBean = m_serverHelper
498: .getServerInfoMBean(m_connectCntx);
499: m_connectCntx.removeNotificationListener(infoMBean, this );
500: } catch (Exception e) {/**/
501: }
502: }
503:
504: /**
505: * JMX callback notifying that the server has transitioned from started->active.
506: */
507: public void handleNotification(Notification notice, Object handback) {
508: if (notice instanceof AttributeChangeNotification) {
509: AttributeChangeNotification acn = (AttributeChangeNotification) notice;
510:
511: if (acn.getAttributeType().equals(
512: "jmx.terracotta.L2.active")) {
513: m_active = true;
514: removeActivationListener();
515: if (m_connectListener != null) {
516: m_connectListener.handleConnection();
517: }
518: } else if (acn.getAttributeType().equals(
519: "jmx.terracotta.L2.passive-uninitialized")) {
520: m_passiveUninitialized = true;
521: if (m_connectListener != null) {
522: m_connectListener.handleConnection();
523: }
524: } else if (acn.getAttributeType().equals(
525: "jmx.terracotta.L2.passive-standby")) {
526: m_passiveUninitialized = false;
527: m_passiveStandby = true;
528: if (m_connectListener != null) {
529: m_connectListener.handleConnection();
530: }
531: }
532: }
533: }
534:
535: public String toString() {
536: return getHostname() + ":" + getJMXPortNumber();
537: }
538:
539: public String getStatusString() {
540: StringBuffer sb = new StringBuffer(this .toString());
541: sb.append(":");
542: sb.append("connected=");
543: sb.append(m_connected);
544: if (m_connected) {
545: sb.append(",status=");
546: if (m_active)
547: sb.append("active");
548: else if (m_passiveUninitialized)
549: sb.append("passive-uninitialized");
550: else if (m_passiveStandby)
551: sb.append("passive-standby");
552: else if (m_started)
553: sb.append("started");
554: }
555: return sb.toString();
556: }
557:
558: public void dump(String prefix) {
559: System.out.println(prefix + this + ":connected=" + m_connected
560: + ",autoConnect=" + m_autoConnect + ",started="
561: + m_started + ",exception=" + m_connectException);
562: }
563:
564: void cancelActiveServices() {
565: cancelConnectThread();
566: cancelConnectionMonitor();
567:
568: if (m_started) {
569: removeActivationListener();
570: }
571: if (m_connectCntx != null) {
572: m_connectCntx.reset();
573: }
574: }
575:
576: public void tearDown() {
577: cancelActiveServices();
578:
579: m_l2Info = null;
580: m_serverHelper = null;
581: m_connectCntx = null;
582: m_connectListener = null;
583: m_connectThread = null;
584: }
585:
586: // --------------------------------------------------------------------------------
587:
588: public static interface AutoConnectListener extends EventListener {
589: void handleEvent();
590: }
591: }
|