View Javadoc

1   /***
2    * 
3    * Copyright RAJD Consultancy Ltd
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
6    * the License. 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 distributed under the License is distributed on
11   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
12   * specific language governing permissions and limitations under the License.
13   * 
14   */
15  package org.logicblaze.lingo.jmx.remote.jms;
16  
17  import java.io.IOException;
18  import java.net.URI;
19  import java.util.Map;
20  import javax.jms.ConnectionFactory;
21  import javax.jms.Destination;
22  import javax.jms.JMSException;
23  import javax.management.ListenerNotFoundException;
24  import javax.management.MBeanServerConnection;
25  import javax.management.NotificationBroadcaster;
26  import javax.management.NotificationBroadcasterSupport;
27  import javax.management.NotificationEmitter;
28  import javax.management.NotificationFilter;
29  import javax.management.NotificationListener;
30  import javax.management.remote.JMXConnectionNotification;
31  import javax.management.remote.JMXConnector;
32  import javax.management.remote.JMXConnectorFactory;
33  import javax.management.remote.JMXServerErrorException;
34  import javax.management.remote.JMXServiceURL;
35  import javax.security.auth.Subject;
36  import org.apache.activemq.ActiveMQConnectionFactory;
37  import org.apache.activemq.command.ActiveMQTopic;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.logicblaze.lingo.jms.JmsProducerConfig;
41  import org.logicblaze.lingo.jms.JmsProxyFactoryBean;
42  import org.logicblaze.lingo.jms.impl.MultiplexingRequestor;
43  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicLong;
44  /***
45   * <p>
46   * The client end of a JMX API connector. An object of this type can be used to establish a connection to a connector
47   * server.
48   * </p>
49   * 
50   * <p>
51   * A newly-created object of this type is unconnected. Its {@link#connect connect} method must be called before it can
52   * be used. However, objects created by {@link JMXConnectorFactory#connect(JMXServiceURL, Map)
53   * JMXConnectorFactory.connect} are already connected.
54   * </p>
55   * 
56   * @since 1.5
57   * @since.unbundled 1.0
58   */
59  public class JmsJmxConnector implements JMXConnector{
60      private static final Log log=LogFactory.getLog(JMXConnector.class);
61      private NotificationBroadcasterSupport connectionNotifier=new NotificationBroadcasterSupport();
62      private AtomicLong notificationNumber=new AtomicLong();
63      private Map env;
64      private String destinationName;
65      private String destinationGroupName=JmsJmxConnectorSupport.MBEAN_GROUP_NAME;
66      private String destinationServerName=JmsJmxConnectorSupport.MBEAN_SERVER_NAME;
67      private URI jmsURL;
68      private MultiplexingRequestor requestor;
69      private JmsProxyFactoryBean proxy;
70      private MBeanJmsServerConnectionClient client;
71      private boolean connected;
72  
73      /***
74       * Create a JmsJmxConnector
75       * 
76       * @param env
77       * @param url
78       * @throws IOException
79       */
80      public JmsJmxConnector(Map env,JMXServiceURL url) throws IOException{
81          this.env=env;
82          this.jmsURL=JmsJmxConnectorSupport.getProviderURL(url);
83          // set any props in the url
84          JmsJmxConnectorSupport.populateProperties(this,jmsURL);
85      }
86  
87      /***
88       * <p>
89       * Establishes the connection to the connector server. This method is equivalent to {@link #connect(Map)
90       * connect(null)}.
91       * </p>
92       * 
93       * @exception IOException
94       *                if the connection could not be made because of a communication problem.
95       * 
96       * @exception SecurityException
97       *                if the connection could not be made for security reasons.
98       */
99      public void connect() throws IOException{
100         connect(this.env);
101     }
102 
103     /***
104      * <p>
105      * Establishes the connection to the connector server.
106      * </p>
107      * 
108      * <p>
109      * If <code>connect</code> has already been called successfully on this object, calling it again has no effect.
110      * If, however, {@link #close} was called after <code>connect</code>, the new <code>connect</code> will throw
111      * an <code>IOException</code>.
112      * <p>
113      * 
114      * <p>
115      * Otherwise, either <code>connect</code> has never been called on this object, or it has been called but produced
116      * an exception. Then calling <code>connect</code> will attempt to establish a connection to the connector server.
117      * </p>
118      * 
119      * @param env
120      *            the properties of the connection. Properties in this map override properties in the map specified when
121      *            the <code>JMXConnector</code> was created, if any. This parameter can be null, which is equivalent
122      *            to an empty map.
123      * 
124      * @exception IOException
125      *                if the connection could not be made because of a communication problem.
126      * 
127      * @exception SecurityException
128      *                if the connection could not be made for security reasons.
129      */
130     public void connect(Map env) throws IOException{
131         if(!connected){
132             proxy=new JmsProxyFactoryBean();
133             proxy.setServiceInterface(javax.management.MBeanServerConnection.class);
134             ConnectionFactory fac=new ActiveMQConnectionFactory(jmsURL);
135             if(destinationName==null){
136                 destinationName=JmsJmxConnectorSupport.DEFAULT_DESTINATION_PREFIX+destinationGroupName+"."
137                                 +destinationServerName;
138             }
139             try{
140                 // this will start all the gubbins
141                 Destination destination=new ActiveMQTopic(destinationName);
142                 proxy.setDestination(destination);
143                 proxy.setConnectionFactory(fac);
144                 requestor=(MultiplexingRequestor) MultiplexingRequestor.newInstance(fac,new JmsProducerConfig(),
145                                 destination);
146                 proxy.setRequestor(requestor);
147                 proxy.setServiceInterface(MBeanJmsServerConnection.class);
148                 proxy.afterPropertiesSet();
149                 sendConnectionNotificationOpened();
150                 client=new MBeanJmsServerConnectionClient((MBeanJmsServerConnection) proxy.getObject(),requestor
151                                 .getConnection());
152             }catch(JMSException e){
153                 log.error("Failed to connect: "+e,e);
154                 IOException ioe=new IOException(e.getMessage());
155                 throw ioe;
156             }
157             connected=true;
158         }
159     }
160 
161     /***
162      * <p>
163      * Returns an <code>MBeanServerConnection</code> object representing a remote MBean server. For a given
164      * <code>JMXConnector</code>, two successful calls to this method will usually return the same
165      * <code>MBeanServerConnection</code> object, though this is not required.
166      * </p>
167      * 
168      * <p>
169      * For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the
170      * corresponding method to be called in the remote MBean server. The value returned by the MBean server method is
171      * the value returned to the client. If the MBean server method produces an <code>Exception</code>, the same
172      * <code>Exception</code> is seen by the client. If the MBean server method, or the attempt to call it, produces
173      * an <code>Error</code>, the <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is
174      * seen by the client.
175      * </p>
176      * 
177      * <p>
178      * Calling this method is equivalent to calling
179      * {@link #getMBeanServerConnection(Subject) getMBeanServerConnection(null)} meaning that no delegation subject is
180      * specified and that all the operations called on the <code>MBeanServerConnection</code> must use the
181      * authenticated subject, if any.
182      * </p>
183      * 
184      * @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to
185      *         the remote MBean server.
186      */
187     public MBeanServerConnection getMBeanServerConnection(){
188         return client;
189     }
190 
191     /***
192      * <p>
193      * Returns an <code>MBeanServerConnection</code> object representing a remote MBean server on which operations are
194      * performed on behalf of the supplied delegation subject. For a given <code>JMXConnector</code> and
195      * <code>Subject</code>, two successful calls to this method will usually return the same
196      * <code>MBeanServerConnection</code> object, though this is not required.
197      * </p>
198      * 
199      * <p>
200      * For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the
201      * corresponding method to be called in the remote MBean server on behalf of the given delegation subject instead of
202      * the authenticated subject. The value returned by the MBean server method is the value returned to the client. If
203      * the MBean server method produces an <code>Exception</code>, the same <code>Exception</code> is seen by the
204      * client. If the MBean server method, or the attempt to call it, produces an <code>Error</code>, the
205      * <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is seen by the client.
206      * </p>
207      * 
208      * @param delegationSubject
209      *            the <code>Subject</code> on behalf of which requests will be performed. Can be null, in which case
210      *            requests will be performed on behalf of the authenticated Subject, if any.
211      * 
212      * @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to
213      *         the remote MBean server on behalf of a given delegation subject.
214      */
215     public MBeanServerConnection getMBeanServerConnection(Subject delegationSubject){
216         throw new UnsupportedOperationException();
217     }
218 
219     /***
220      * <p>
221      * Closes the client connection to its server. Any ongoing or new request using the MBeanServerConnection returned
222      * by {@link #getMBeanServerConnection()} will get an <code>IOException</code>.
223      * </p>
224      * 
225      * <p>
226      * If <code>close</code> has already been called successfully on this object, calling it again has no effect. If
227      * <code>close</code> has never been called, or if it was called but produced an exception, an attempt will be
228      * made to close the connection. This attempt can succeed, in which case <code>close</code> will return normally,
229      * or it can generate an exception.
230      * </p>
231      * 
232      * <p>
233      * Closing a connection is a potentially slow operation. For example, if the server has crashed, the close operation
234      * might have to wait for a network protocol timeout. Callers that do not want to block in a close operation should
235      * do it in a separate thread.
236      * </p>
237      * 
238      * @exception IOException
239      *                if the connection cannot be closed cleanly. If this exception is thrown, it is not known whether
240      *                the server end of the connection has been cleanly closed.
241      */
242     public void close() throws IOException{
243         if(connected){
244             connected=false;
245             try{
246                 sendConnectionNotificationClosed();
247                 proxy.destroy();
248             }catch(Exception e){
249                 log.error("Failed to destroy proxy: "+e,e);
250                 throw new IOException(e.getMessage());
251             }
252         }
253     }
254 
255     /***
256      * <p>
257      * Adds a listener to be informed of changes in connection status. The listener will receive notifications of type
258      * {@link JMXConnectionNotification}. An implementation can send other types of notifications too.
259      * </p>
260      * 
261      * <p>
262      * Any number of listeners can be added with this method. The same listener can be added more than once with the
263      * same or different values for the filter and handback. There is no special treatment of a duplicate entry. For
264      * example, if a listener is registered twice with no filter, then its <code>handleNotification</code> method will
265      * be called twice for each notification.
266      * </p>
267      * 
268      * @param listener
269      *            a listener to receive connection status notifications.
270      * @param filter
271      *            a filter to select which notifications are to be delivered to the listener, or null if all
272      *            notifications are to be delivered.
273      * @param handback
274      *            an object to be given to the listener along with each notification. Can be null.
275      * 
276      * @exception NullPointerException
277      *                if <code>listener</code> is null.
278      * 
279      * @see #removeConnectionNotificationListener
280      * @see NotificationBroadcaster#addNotificationListener
281      */
282     public void addConnectionNotificationListener(NotificationListener listener,NotificationFilter filter,
283                     Object handback){
284         connectionNotifier.addNotificationListener(listener,filter,handback);
285     }
286 
287     /***
288      * <p>
289      * Removes a listener from the list to be informed of changes in status. The listener must previously have been
290      * added. If there is more than one matching listener, all are removed.
291      * </p>
292      * 
293      * @param listener
294      *            a listener to receive connection status notifications.
295      * 
296      * @exception NullPointerException
297      *                if <code>listener</code> is null.
298      * 
299      * @exception ListenerNotFoundException
300      *                if the listener is not registered with this <code>JMXConnector</code>.
301      * 
302      * @see #removeConnectionNotificationListener(NotificationListener, NotificationFilter, Object)
303      * @see #addConnectionNotificationListener
304      * @see NotificationEmitter#removeNotificationListener
305      */
306     public void removeConnectionNotificationListener(NotificationListener listener) throws ListenerNotFoundException{
307         connectionNotifier.removeNotificationListener(listener);
308     }
309 
310     /***
311      * <p>
312      * Removes a listener from the list to be informed of changes in status. The listener must previously have been
313      * added with the same three parameters. If there is more than one matching listener, only one is removed.
314      * </p>
315      * 
316      * @param l
317      *            a listener to receive connection status notifications.
318      * @param f
319      *            a filter to select which notifications are to be delivered to the listener. Can be null.
320      * @param handback
321      *            an object to be given to the listener along with each notification. Can be null.
322      * 
323      * @exception ListenerNotFoundException
324      *                if the listener is not registered with this <code>JMXConnector</code>, or is not registered
325      *                with the given filter and handback.
326      * 
327      * @see #removeConnectionNotificationListener(NotificationListener)
328      * @see #addConnectionNotificationListener
329      * @see NotificationEmitter#removeNotificationListener
330      */
331     public void removeConnectionNotificationListener(NotificationListener l,NotificationFilter f,Object handback)
332                     throws ListenerNotFoundException{
333         connectionNotifier.removeNotificationListener(l,f,handback);
334     }
335 
336     /***
337      * <p>
338      * Gets this connection's ID from the connector server. For a given connector server, every connection will have a
339      * unique id which does not change during the lifetime of the connection.
340      * </p>
341      * 
342      * @return the unique ID of this connection. This is the same as the ID that the connector server includes in its
343      *         {@link JMXConnectionNotification}s. The {@link javax.management.remote package description} describes the
344      *         conventions for connection IDs.
345      */
346     public String getConnectionId(){
347         try{
348             return requestor.getConnection().getClientID();
349         }catch(JMSException e){
350             log.error("Failed to get clientID ",e);
351             throw new RuntimeException(e);
352         }
353     }
354 
355     private void sendConnectionNotificationOpened(){
356         JMXConnectionNotification notification=new JMXConnectionNotification(JMXConnectionNotification.OPENED,this,
357                         getConnectionId(),notificationNumber.incrementAndGet(),"Connection opened",null);
358         connectionNotifier.sendNotification(notification);
359     }
360 
361     private void sendConnectionNotificationClosed(){
362         JMXConnectionNotification notification=new JMXConnectionNotification(JMXConnectionNotification.CLOSED,this,
363                         getConnectionId(),notificationNumber.incrementAndGet(),"Connection closed",null);
364         connectionNotifier.sendNotification(notification);
365     }
366 
367     private void sendConnectionNotificationFailed(String message){
368         JMXConnectionNotification notification=new JMXConnectionNotification(JMXConnectionNotification.FAILED,this,
369                         getConnectionId(),notificationNumber.incrementAndGet(),message,null);
370         connectionNotifier.sendNotification(notification);
371     }
372 }