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