1 /*
2  * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package javax.management.remote;
28 
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 
34 import javax.management.MBeanNotificationInfo;
35 import javax.management.MBeanRegistration;
36 import javax.management.MBeanServer;
37 import javax.management.Notification;
38 import javax.management.NotificationBroadcasterSupport;
39 import javax.management.ObjectName;
40 
41 /**
42  * <p>Superclass of every connector server.  A connector server is
43  * attached to an MBean server.  It listens for client connection
44  * requests and creates a connection for each one.</p>
45  *
46  * <p>A connector server is associated with an MBean server either by
47  * registering it in that MBean server, or by passing the MBean server
48  * to its constructor.</p>
49  *
50  * <p>A connector server is inactive when created.  It only starts
51  * listening for client connections when the {@link #start() start}
52  * method is called.  A connector server stops listening for client
53  * connections when the {@link #stop() stop} method is called or when
54  * the connector server is unregistered from its MBean server.</p>
55  *
56  * <p>Stopping a connector server does not unregister it from its
57  * MBean server.  A connector server once stopped cannot be
58  * restarted.</p>
59  *
60  * <p>Each time a client connection is made or broken, a notification
61  * of class {@link JMXConnectionNotification} is emitted.</p>
62  *
63  * @since 1.5
64  */
65 public abstract class JMXConnectorServer
66         extends NotificationBroadcasterSupport
67         implements JMXConnectorServerMBean, MBeanRegistration, JMXAddressable {
68 
69     /**
70      * <p>Name of the attribute that specifies the authenticator for a
71      * connector server.  The value associated with this attribute, if
72      * any, must be an object that implements the interface {@link
73      * JMXAuthenticator}.</p>
74      */
75     public static final String AUTHENTICATOR =
76         "jmx.remote.authenticator";
77 
78     /**
79      * <p>Constructs a connector server that will be registered as an
80      * MBean in the MBean server it is attached to.  This constructor
81      * is typically called by one of the <code>createMBean</code>
82      * methods when creating, within an MBean server, a connector
83      * server that makes it available remotely.</p>
84      */
JMXConnectorServer()85     public JMXConnectorServer() {
86         this(null);
87     }
88 
89     /**
90      * <p>Constructs a connector server that is attached to the given
91      * MBean server.  A connector server that is created in this way
92      * can be registered in a different MBean server, or not registered
93      * in any MBean server.</p>
94      *
95      * @param mbeanServer the MBean server that this connector server
96      * is attached to.  Null if this connector server will be attached
97      * to an MBean server by being registered in it.
98      */
JMXConnectorServer(MBeanServer mbeanServer)99     public JMXConnectorServer(MBeanServer mbeanServer) {
100         this.mbeanServer = mbeanServer;
101     }
102 
103     /**
104      * <p>Returns the MBean server that this connector server is
105      * attached to.</p>
106      *
107      * @return the MBean server that this connector server is attached
108      * to, or null if it is not yet attached to an MBean server.
109      */
getMBeanServer()110     public synchronized MBeanServer getMBeanServer() {
111         return mbeanServer;
112     }
113 
setMBeanServerForwarder(MBeanServerForwarder mbsf)114     public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf)
115     {
116         if (mbsf == null)
117             throw new IllegalArgumentException("Invalid null argument: mbsf");
118 
119         if (mbeanServer !=  null) mbsf.setMBeanServer(mbeanServer);
120         mbeanServer = mbsf;
121     }
122 
getConnectionIds()123     public String[] getConnectionIds() {
124         synchronized (connectionIds) {
125             return connectionIds.toArray(new String[connectionIds.size()]);
126         }
127     }
128 
129     /**
130      * <p>Returns a client stub for this connector server.  A client
131      * stub is a serializable object whose {@link
132      * JMXConnector#connect(Map) connect} method can be used to make
133      * one new connection to this connector server.</p>
134      *
135      * <p>A given connector need not support the generation of client
136      * stubs.  However, the connectors specified by the JMX Remote API do
137      * (JMXMP Connector and RMI Connector).</p>
138      *
139      * <p>The default implementation of this method uses {@link
140      * #getAddress} and {@link JMXConnectorFactory} to generate the
141      * stub, with code equivalent to the following:</p>
142      *
143      * <pre>
144      * JMXServiceURL addr = {@link #getAddress() getAddress()};
145      * return {@link JMXConnectorFactory#newJMXConnector(JMXServiceURL, Map)
146      *          JMXConnectorFactory.newJMXConnector(addr, env)};
147      * </pre>
148      *
149      * <p>A connector server for which this is inappropriate must
150      * override this method so that it either implements the
151      * appropriate logic or throws {@link
152      * UnsupportedOperationException}.</p>
153      *
154      * @param env client connection parameters of the same sort that
155      * could be provided to {@link JMXConnector#connect(Map)
156      * JMXConnector.connect(Map)}.  Can be null, which is equivalent
157      * to an empty map.
158      *
159      * @return a client stub that can be used to make a new connection
160      * to this connector server.
161      *
162      * @exception UnsupportedOperationException if this connector
163      * server does not support the generation of client stubs.
164      *
165      * @exception IllegalStateException if the JMXConnectorServer is
166      * not started (see {@link JMXConnectorServerMBean#isActive()}).
167      *
168      * @exception IOException if a communications problem means that a
169      * stub cannot be created.
170      **/
toJMXConnector(Map<String,?> env)171     public JMXConnector toJMXConnector(Map<String,?> env)
172         throws IOException
173     {
174         if (!isActive()) throw new
175             IllegalStateException("Connector is not active");
176         JMXServiceURL addr = getAddress();
177         return JMXConnectorFactory.newJMXConnector(addr, env);
178     }
179 
180     /**
181      * <p>Returns an array indicating the notifications that this MBean
182      * sends. The implementation in <code>JMXConnectorServer</code>
183      * returns an array with one element, indicating that it can emit
184      * notifications of class {@link JMXConnectionNotification} with
185      * the types defined in that class.  A subclass that can emit other
186      * notifications should return an array that contains this element
187      * plus descriptions of the other notifications.</p>
188      *
189      * @return the array of possible notifications.
190      */
191     @Override
getNotificationInfo()192     public MBeanNotificationInfo[] getNotificationInfo() {
193         final String[] types = {
194             JMXConnectionNotification.OPENED,
195             JMXConnectionNotification.CLOSED,
196             JMXConnectionNotification.FAILED,
197         };
198         final String className = JMXConnectionNotification.class.getName();
199         final String description =
200             "A client connection has been opened or closed";
201         return new MBeanNotificationInfo[] {
202             new MBeanNotificationInfo(types, className, description),
203         };
204     }
205 
206     /**
207      * <p>Called by a subclass when a new client connection is opened.
208      * Adds <code>connectionId</code> to the list returned by {@link
209      * #getConnectionIds()}, then emits a {@link
210      * JMXConnectionNotification} with type {@link
211      * JMXConnectionNotification#OPENED}.</p>
212      *
213      * @param connectionId the ID of the new connection.  This must be
214      * different from the ID of any connection previously opened by
215      * this connector server.
216      *
217      * @param message the message for the emitted {@link
218      * JMXConnectionNotification}.  Can be null.  See {@link
219      * Notification#getMessage()}.
220      *
221      * @param userData the <code>userData</code> for the emitted
222      * {@link JMXConnectionNotification}.  Can be null.  See {@link
223      * Notification#getUserData()}.
224      *
225      * @exception NullPointerException if <code>connectionId</code> is
226      * null.
227      */
connectionOpened(String connectionId, String message, Object userData)228     protected void connectionOpened(String connectionId,
229                                     String message,
230                                     Object userData) {
231 
232         if (connectionId == null)
233             throw new NullPointerException("Illegal null argument");
234 
235         synchronized (connectionIds) {
236             connectionIds.add(connectionId);
237         }
238 
239         sendNotification(JMXConnectionNotification.OPENED, connectionId,
240                          message, userData);
241     }
242 
243     /**
244      * <p>Called by a subclass when a client connection is closed
245      * normally.  Removes <code>connectionId</code> from the list returned
246      * by {@link #getConnectionIds()}, then emits a {@link
247      * JMXConnectionNotification} with type {@link
248      * JMXConnectionNotification#CLOSED}.</p>
249      *
250      * @param connectionId the ID of the closed connection.
251      *
252      * @param message the message for the emitted {@link
253      * JMXConnectionNotification}.  Can be null.  See {@link
254      * Notification#getMessage()}.
255      *
256      * @param userData the <code>userData</code> for the emitted
257      * {@link JMXConnectionNotification}.  Can be null.  See {@link
258      * Notification#getUserData()}.
259      *
260      * @exception NullPointerException if <code>connectionId</code>
261      * is null.
262      */
connectionClosed(String connectionId, String message, Object userData)263     protected void connectionClosed(String connectionId,
264                                     String message,
265                                     Object userData) {
266 
267         if (connectionId == null)
268             throw new NullPointerException("Illegal null argument");
269 
270         synchronized (connectionIds) {
271             connectionIds.remove(connectionId);
272         }
273 
274         sendNotification(JMXConnectionNotification.CLOSED, connectionId,
275                          message, userData);
276     }
277 
278     /**
279      * <p>Called by a subclass when a client connection fails.
280      * Removes <code>connectionId</code> from the list returned by
281      * {@link #getConnectionIds()}, then emits a {@link
282      * JMXConnectionNotification} with type {@link
283      * JMXConnectionNotification#FAILED}.</p>
284      *
285      * @param connectionId the ID of the failed connection.
286      *
287      * @param message the message for the emitted {@link
288      * JMXConnectionNotification}.  Can be null.  See {@link
289      * Notification#getMessage()}.
290      *
291      * @param userData the <code>userData</code> for the emitted
292      * {@link JMXConnectionNotification}.  Can be null.  See {@link
293      * Notification#getUserData()}.
294      *
295      * @exception NullPointerException if <code>connectionId</code> is
296      * null.
297      */
connectionFailed(String connectionId, String message, Object userData)298     protected void connectionFailed(String connectionId,
299                                     String message,
300                                     Object userData) {
301 
302         if (connectionId == null)
303             throw new NullPointerException("Illegal null argument");
304 
305         synchronized (connectionIds) {
306             connectionIds.remove(connectionId);
307         }
308 
309         sendNotification(JMXConnectionNotification.FAILED, connectionId,
310                          message, userData);
311     }
312 
sendNotification(String type, String connectionId, String message, Object userData)313     private void sendNotification(String type, String connectionId,
314                                   String message, Object userData) {
315         Notification notif =
316             new JMXConnectionNotification(type,
317                                           getNotificationSource(),
318                                           connectionId,
319                                           nextSequenceNumber(),
320                                           message,
321                                           userData);
322         sendNotification(notif);
323     }
324 
getNotificationSource()325     private synchronized Object getNotificationSource() {
326         if (myName != null)
327             return myName;
328         else
329             return this;
330     }
331 
nextSequenceNumber()332     private static long nextSequenceNumber() {
333         synchronized (sequenceNumberLock) {
334             return sequenceNumber++;
335         }
336     }
337 
338     // implements MBeanRegistration
339     /**
340      * <p>Called by an MBean server when this connector server is
341      * registered in that MBean server.  This connector server becomes
342      * attached to the MBean server and its {@link #getMBeanServer()}
343      * method will return <code>mbs</code>.</p>
344      *
345      * <p>If this connector server is already attached to an MBean
346      * server, this method has no effect.  The MBean server it is
347      * attached to is not necessarily the one it is being registered
348      * in.</p>
349      *
350      * @param mbs the MBean server in which this connection server is
351      * being registered.
352      *
353      * @param name The object name of the MBean.
354      *
355      * @return The name under which the MBean is to be registered.
356      *
357      * @exception NullPointerException if <code>mbs</code> or
358      * <code>name</code> is null.
359      */
preRegister(MBeanServer mbs, ObjectName name)360     public synchronized ObjectName preRegister(MBeanServer mbs,
361                                                ObjectName name) {
362         if (mbs == null || name == null)
363             throw new NullPointerException("Null MBeanServer or ObjectName");
364         if (mbeanServer == null) {
365             mbeanServer = mbs;
366             myName = name;
367         }
368         return name;
369     }
370 
postRegister(Boolean registrationDone)371     public void postRegister(Boolean registrationDone) {
372         // do nothing
373     }
374 
375     /**
376      * <p>Called by an MBean server when this connector server is
377      * unregistered from that MBean server.  If this connector server
378      * was attached to that MBean server by being registered in it,
379      * and if the connector server is still active,
380      * then unregistering it will call the {@link #stop stop} method.
381      * If the <code>stop</code> method throws an exception, the
382      * unregistration attempt will fail.  It is recommended to call
383      * the <code>stop</code> method explicitly before unregistering
384      * the MBean.</p>
385      *
386      * @exception IOException if thrown by the {@link #stop stop} method.
387      */
preDeregister()388     public synchronized void preDeregister() throws Exception {
389         if (myName != null && isActive()) {
390             stop();
391             myName = null; // just in case stop is buggy and doesn't stop
392         }
393     }
394 
postDeregister()395     public void postDeregister() {
396         myName = null;
397     }
398 
399     /**
400      * The MBeanServer used by this server to execute a client request.
401      */
402     private MBeanServer mbeanServer = null;
403 
404     /**
405      * The name used to registered this server in an MBeanServer.
406      * It is null if the this server is not registered or has been unregistered.
407      */
408     private ObjectName myName;
409 
410     private final List<String> connectionIds = new ArrayList<String>();
411 
412     private static final int[] sequenceNumberLock = new int[0];
413     private static long sequenceNumber;
414 }
415