Java tutorial
/** * * Copyright RAJD Consultancy Ltd * * 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.logicblaze.lingo.jmx.remote.jms; import java.io.IOException; import java.net.URI; import java.util.Map; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.management.ListenerNotFoundException; import javax.management.MBeanServerConnection; import javax.management.NotificationBroadcaster; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServerErrorException; import javax.management.remote.JMXServiceURL; import javax.security.auth.Subject; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQTopic; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.logicblaze.lingo.jms.JmsProducerConfig; import org.logicblaze.lingo.jms.JmsProxyFactoryBean; import org.logicblaze.lingo.jms.impl.MultiplexingRequestor; import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicLong; /** * <p> * The client end of a JMX API connector. An object of this type can be used to establish a connection to a connector * server. * </p> * * <p> * A newly-created object of this type is unconnected. Its {@link#connect connect} method must be called before it can * be used. However, objects created by {@link JMXConnectorFactory#connect(JMXServiceURL, Map) * JMXConnectorFactory.connect} are already connected. * </p> * * @since 1.5 * @since.unbundled 1.0 */ public class JmsJmxConnector implements JMXConnector { private static final Log log = LogFactory.getLog(JMXConnector.class); private NotificationBroadcasterSupport connectionNotifier = new NotificationBroadcasterSupport(); private AtomicLong notificationNumber = new AtomicLong(); private Map env; private String destinationName; private String destinationGroupName = JmsJmxConnectorSupport.MBEAN_GROUP_NAME; private String destinationServerName = JmsJmxConnectorSupport.MBEAN_SERVER_NAME; private URI jmsURL; private MultiplexingRequestor requestor; private JmsProxyFactoryBean proxy; private MBeanJmsServerConnectionClient client; private boolean connected; /** * Create a JmsJmxConnector * * @param env * @param url * @throws IOException */ public JmsJmxConnector(Map env, JMXServiceURL url) throws IOException { this.env = env; this.jmsURL = JmsJmxConnectorSupport.getProviderURL(url); // set any props in the url JmsJmxConnectorSupport.populateProperties(this, jmsURL); } /** * <p> * Establishes the connection to the connector server. This method is equivalent to {@link #connect(Map) * connect(null)}. * </p> * * @exception IOException * if the connection could not be made because of a communication problem. * * @exception SecurityException * if the connection could not be made for security reasons. */ public void connect() throws IOException { connect(this.env); } /** * <p> * Establishes the connection to the connector server. * </p> * * <p> * If <code>connect</code> has already been called successfully on this object, calling it again has no effect. * If, however, {@link #close} was called after <code>connect</code>, the new <code>connect</code> will throw * an <code>IOException</code>. * <p> * * <p> * Otherwise, either <code>connect</code> has never been called on this object, or it has been called but produced * an exception. Then calling <code>connect</code> will attempt to establish a connection to the connector server. * </p> * * @param env * the properties of the connection. Properties in this map override properties in the map specified when * the <code>JMXConnector</code> was created, if any. This parameter can be null, which is equivalent * to an empty map. * * @exception IOException * if the connection could not be made because of a communication problem. * * @exception SecurityException * if the connection could not be made for security reasons. */ public void connect(Map env) throws IOException { if (!connected) { proxy = new JmsProxyFactoryBean(); proxy.setServiceInterface(javax.management.MBeanServerConnection.class); ConnectionFactory fac = new ActiveMQConnectionFactory(jmsURL); if (destinationName == null) { destinationName = JmsJmxConnectorSupport.DEFAULT_DESTINATION_PREFIX + destinationGroupName + "." + destinationServerName; } try { // this will start all the gubbins Destination destination = new ActiveMQTopic(destinationName); proxy.setDestination(destination); proxy.setConnectionFactory(fac); requestor = (MultiplexingRequestor) MultiplexingRequestor.newInstance(fac, new JmsProducerConfig(), destination); proxy.setRequestor(requestor); proxy.setServiceInterface(MBeanJmsServerConnection.class); proxy.afterPropertiesSet(); sendConnectionNotificationOpened(); client = new MBeanJmsServerConnectionClient((MBeanJmsServerConnection) proxy.getObject(), requestor.getConnection()); } catch (JMSException e) { log.error("Failed to connect: " + e, e); IOException ioe = new IOException(e.getMessage()); throw ioe; } connected = true; } } /** * <p> * Returns an <code>MBeanServerConnection</code> object representing a remote MBean server. For a given * <code>JMXConnector</code>, two successful calls to this method will usually return the same * <code>MBeanServerConnection</code> object, though this is not required. * </p> * * <p> * For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the * corresponding method to be called in the remote MBean server. The value returned by the MBean server method is * the value returned to the client. If the MBean server method produces an <code>Exception</code>, the same * <code>Exception</code> is seen by the client. If the MBean server method, or the attempt to call it, produces * an <code>Error</code>, the <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is * seen by the client. * </p> * * <p> * Calling this method is equivalent to calling * {@link #getMBeanServerConnection(Subject) getMBeanServerConnection(null)} meaning that no delegation subject is * specified and that all the operations called on the <code>MBeanServerConnection</code> must use the * authenticated subject, if any. * </p> * * @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to * the remote MBean server. */ public MBeanServerConnection getMBeanServerConnection() { return client; } /** * <p> * Returns an <code>MBeanServerConnection</code> object representing a remote MBean server on which operations are * performed on behalf of the supplied delegation subject. For a given <code>JMXConnector</code> and * <code>Subject</code>, two successful calls to this method will usually return the same * <code>MBeanServerConnection</code> object, though this is not required. * </p> * * <p> * For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the * corresponding method to be called in the remote MBean server on behalf of the given delegation subject instead of * the authenticated subject. The value returned by the MBean server method is the value returned to the client. If * the MBean server method produces an <code>Exception</code>, the same <code>Exception</code> is seen by the * client. If the MBean server method, or the attempt to call it, produces an <code>Error</code>, the * <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is seen by the client. * </p> * * @param delegationSubject * the <code>Subject</code> on behalf of which requests will be performed. Can be null, in which case * requests will be performed on behalf of the authenticated Subject, if any. * * @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to * the remote MBean server on behalf of a given delegation subject. */ public MBeanServerConnection getMBeanServerConnection(Subject delegationSubject) { throw new UnsupportedOperationException(); } /** * <p> * Closes the client connection to its server. Any ongoing or new request using the MBeanServerConnection returned * by {@link #getMBeanServerConnection()} will get an <code>IOException</code>. * </p> * * <p> * If <code>close</code> has already been called successfully on this object, calling it again has no effect. If * <code>close</code> has never been called, or if it was called but produced an exception, an attempt will be * made to close the connection. This attempt can succeed, in which case <code>close</code> will return normally, * or it can generate an exception. * </p> * * <p> * Closing a connection is a potentially slow operation. For example, if the server has crashed, the close operation * might have to wait for a network protocol timeout. Callers that do not want to block in a close operation should * do it in a separate thread. * </p> * * @exception IOException * if the connection cannot be closed cleanly. If this exception is thrown, it is not known whether * the server end of the connection has been cleanly closed. */ public void close() throws IOException { if (connected) { connected = false; try { sendConnectionNotificationClosed(); proxy.destroy(); } catch (Exception e) { log.error("Failed to destroy proxy: " + e, e); throw new IOException(e.getMessage()); } } } /** * <p> * Adds a listener to be informed of changes in connection status. The listener will receive notifications of type * {@link JMXConnectionNotification}. An implementation can send other types of notifications too. * </p> * * <p> * Any number of listeners can be added with this method. The same listener can be added more than once with the * same or different values for the filter and handback. There is no special treatment of a duplicate entry. For * example, if a listener is registered twice with no filter, then its <code>handleNotification</code> method will * be called twice for each notification. * </p> * * @param listener * a listener to receive connection status notifications. * @param filter * a filter to select which notifications are to be delivered to the listener, or null if all * notifications are to be delivered. * @param handback * an object to be given to the listener along with each notification. Can be null. * * @exception NullPointerException * if <code>listener</code> is null. * * @see #removeConnectionNotificationListener * @see NotificationBroadcaster#addNotificationListener */ public void addConnectionNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) { connectionNotifier.addNotificationListener(listener, filter, handback); } /** * <p> * Removes a listener from the list to be informed of changes in status. The listener must previously have been * added. If there is more than one matching listener, all are removed. * </p> * * @param listener * a listener to receive connection status notifications. * * @exception NullPointerException * if <code>listener</code> is null. * * @exception ListenerNotFoundException * if the listener is not registered with this <code>JMXConnector</code>. * * @see #removeConnectionNotificationListener(NotificationListener, NotificationFilter, Object) * @see #addConnectionNotificationListener * @see NotificationEmitter#removeNotificationListener */ public void removeConnectionNotificationListener(NotificationListener listener) throws ListenerNotFoundException { connectionNotifier.removeNotificationListener(listener); } /** * <p> * Removes a listener from the list to be informed of changes in status. The listener must previously have been * added with the same three parameters. If there is more than one matching listener, only one is removed. * </p> * * @param l * a listener to receive connection status notifications. * @param f * a filter to select which notifications are to be delivered to the listener. Can be null. * @param handback * an object to be given to the listener along with each notification. Can be null. * * @exception ListenerNotFoundException * if the listener is not registered with this <code>JMXConnector</code>, or is not registered * with the given filter and handback. * * @see #removeConnectionNotificationListener(NotificationListener) * @see #addConnectionNotificationListener * @see NotificationEmitter#removeNotificationListener */ public void removeConnectionNotificationListener(NotificationListener l, NotificationFilter f, Object handback) throws ListenerNotFoundException { connectionNotifier.removeNotificationListener(l, f, handback); } /** * <p> * Gets this connection's ID from the connector server. For a given connector server, every connection will have a * unique id which does not change during the lifetime of the connection. * </p> * * @return the unique ID of this connection. This is the same as the ID that the connector server includes in its * {@link JMXConnectionNotification}s. The {@link javax.management.remote package description} describes the * conventions for connection IDs. */ public String getConnectionId() { try { return requestor.getConnection().getClientID(); } catch (JMSException e) { log.error("Failed to get clientID ", e); throw new RuntimeException(e); } } private void sendConnectionNotificationOpened() { JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.OPENED, this, getConnectionId(), notificationNumber.incrementAndGet(), "Connection opened", null); connectionNotifier.sendNotification(notification); } private void sendConnectionNotificationClosed() { JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.CLOSED, this, getConnectionId(), notificationNumber.incrementAndGet(), "Connection closed", null); connectionNotifier.sendNotification(notification); } private void sendConnectionNotificationFailed(String message) { JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.FAILED, this, getConnectionId(), notificationNumber.incrementAndGet(), message, null); connectionNotifier.sendNotification(notification); } }