|
1 /* |
|
2 * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any questions. |
|
24 */ |
|
25 |
|
26 import com.sun.jmx.namespace.ObjectNameRouter; |
|
27 import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR; |
|
28 |
|
29 import java.io.IOException; |
|
30 import java.util.Arrays; |
|
31 import java.util.Map; |
|
32 import java.util.Set; |
|
33 import java.util.TreeSet; |
|
34 import java.util.logging.Level; |
|
35 import java.util.logging.Logger; |
|
36 |
|
37 import javax.management.InstanceAlreadyExistsException; |
|
38 import javax.management.InstanceNotFoundException; |
|
39 import javax.management.JMX; |
|
40 import javax.management.ListenerNotFoundException; |
|
41 import javax.management.MBeanException; |
|
42 import javax.management.MBeanNotificationInfo; |
|
43 import javax.management.MBeanRegistration; |
|
44 import javax.management.MBeanRegistrationException; |
|
45 import javax.management.MBeanServer; |
|
46 import javax.management.MBeanServerConnection; |
|
47 import javax.management.MalformedObjectNameException; |
|
48 import javax.management.NotCompliantMBeanException; |
|
49 import javax.management.Notification; |
|
50 import javax.management.NotificationBroadcasterSupport; |
|
51 import javax.management.NotificationEmitter; |
|
52 import javax.management.NotificationFilter; |
|
53 import javax.management.NotificationListener; |
|
54 import javax.management.ObjectInstance; |
|
55 import javax.management.ObjectName; |
|
56 import javax.management.ReflectionException; |
|
57 import javax.management.namespace.JMXNamespace; |
|
58 import javax.management.namespace.JMXNamespaces; |
|
59 import javax.management.namespace.JMXRemoteNamespaceMBean; |
|
60 import javax.management.remote.JMXServiceURL; |
|
61 |
|
62 /** |
|
63 * The {@code NamespaceController} MBean makes it possible to easily |
|
64 * create mount points ({@linkplain JMXNamespace JMXNamespaces}) in an |
|
65 * {@code MBeanServer}. |
|
66 * There is at most one instance of NamespaceController in an |
|
67 * MBeanServer - which can be created using the {@link #createInstance |
|
68 * createInstance} method. The {@code NamespaceController} MBean will |
|
69 * make it possible to remotely create name spaces by mounting remote |
|
70 * MBeanServers into the MBeanServer in which it was registered. |
|
71 */ |
|
72 // This API was originally in the draft of javax/management/namespaces |
|
73 // but we decided to retire it. Rather than removing all the associated |
|
74 // tests I have moved the API to the test hierarchy - so it is now used as |
|
75 // an additional (though somewhat complex) test case... |
|
76 // |
|
77 public class NamespaceController implements NamespaceControllerMBean, |
|
78 NotificationEmitter, MBeanRegistration { |
|
79 |
|
80 /** |
|
81 * A logger for this class. |
|
82 **/ |
|
83 private static final Logger LOG = |
|
84 Logger.getLogger(NamespaceController.class.getName()); |
|
85 |
|
86 private static long seqNumber=0; |
|
87 |
|
88 private final NotificationBroadcasterSupport broadcaster = |
|
89 new NotificationBroadcasterSupport(); |
|
90 |
|
91 private volatile MBeanServer mbeanServer = null; |
|
92 |
|
93 private volatile ObjectName objectName = null; |
|
94 |
|
95 //was: NamespaceController.class.getPackage().getName() |
|
96 public static final String NAMESPACE_CONTROLLER_DOMAIN = "jmx.ns"; |
|
97 |
|
98 /** |
|
99 * Creates a new NamespaceController. |
|
100 * Using {@link #createInstance} should be preferred. |
|
101 **/ |
|
102 public NamespaceController() { |
|
103 this(null); |
|
104 } |
|
105 |
|
106 public NamespaceController(MBeanServer mbeanServer) { |
|
107 this.mbeanServer = mbeanServer; |
|
108 } |
|
109 |
|
110 /* |
|
111 * MBeanNotification support |
|
112 * You shouldn't update these methods |
|
113 */ |
|
114 public final void addNotificationListener(NotificationListener listener, |
|
115 NotificationFilter filter, Object handback) { |
|
116 broadcaster.addNotificationListener(listener, filter, handback); |
|
117 } |
|
118 |
|
119 public MBeanNotificationInfo[] getNotificationInfo() { |
|
120 return new MBeanNotificationInfo[] { |
|
121 }; |
|
122 } |
|
123 |
|
124 public final void removeNotificationListener(NotificationListener listener) |
|
125 throws ListenerNotFoundException { |
|
126 broadcaster.removeNotificationListener(listener); |
|
127 } |
|
128 |
|
129 public final void removeNotificationListener(NotificationListener listener, |
|
130 NotificationFilter filter, Object handback) |
|
131 throws ListenerNotFoundException { |
|
132 broadcaster.removeNotificationListener(listener, filter, handback); |
|
133 } |
|
134 |
|
135 public static synchronized long getNextSeqNumber() { |
|
136 return seqNumber++; |
|
137 } |
|
138 |
|
139 protected final void sendNotification(Notification n) { |
|
140 if (n.getSequenceNumber()<=0) |
|
141 n.setSequenceNumber(getNextSeqNumber()); |
|
142 if (n.getSource()==null) |
|
143 n.setSource(objectName); |
|
144 broadcaster.sendNotification(n); |
|
145 } |
|
146 |
|
147 /** |
|
148 * The ObjectName with which this MBean was registered. |
|
149 * <p>Unless changed by subclasses, this is |
|
150 * {@code |
|
151 * "javax.management.namespace:type="+this.getClass().getSimpleName()}. |
|
152 * @return this MBean's ObjectName, or null if this MBean was never |
|
153 * registered. |
|
154 **/ |
|
155 public final ObjectName getObjectName() { |
|
156 return objectName; |
|
157 } |
|
158 |
|
159 /** |
|
160 * The MBeanServer served by this NamespaceController. |
|
161 * @return the MBeanServer served by this NamespaceController. |
|
162 **/ |
|
163 public final MBeanServer getMBeanServer() { |
|
164 return mbeanServer; |
|
165 } |
|
166 |
|
167 /** |
|
168 * Allows the MBean to perform any operations it needs before being |
|
169 * registered in the MBean server. If the name of the MBean is not |
|
170 * specified, the MBean can provide a name for its registration. If |
|
171 * any exception is raised, the MBean will not be registered in the |
|
172 * MBean server. Subclasses which override {@code preRegister} |
|
173 * must call {@code super.preRegister(name,server)}; |
|
174 * @param server The MBean server in which the MBean will be registered. |
|
175 * @param name The object name of the MBean. |
|
176 * The name must be either {@code null} - or equal to that |
|
177 * described by {@link #getObjectName}. |
|
178 * @return The name under which the MBean is to be registered. |
|
179 * This will be the name described by {@link #getObjectName}. |
|
180 * @throws MalformedObjectNameException if the supplied name does not |
|
181 * meet expected requirements. |
|
182 */ |
|
183 public ObjectName preRegister(MBeanServer server, ObjectName name) |
|
184 throws MalformedObjectNameException { |
|
185 objectName = name; |
|
186 final ObjectName single = |
|
187 ObjectName.getInstance(NAMESPACE_CONTROLLER_DOMAIN+ |
|
188 ":type="+this.getClass().getSimpleName()); |
|
189 if (name!=null && !single.equals(name)) |
|
190 throw new MalformedObjectNameException(name.toString()); |
|
191 if (mbeanServer == null) mbeanServer = server; |
|
192 return single; |
|
193 } |
|
194 |
|
195 /** |
|
196 * Allows the MBean to perform any operations needed after having |
|
197 * been registered in the MBean server or after the registration has |
|
198 * failed. |
|
199 * @param registrationDone Indicates whether or not the MBean has been |
|
200 * successfully registered in the MBean server. The value false means |
|
201 * that the registration has failed. |
|
202 */ |
|
203 public void postRegister(Boolean registrationDone) { |
|
204 //TODO postRegister implementation; |
|
205 } |
|
206 |
|
207 /** |
|
208 * Allows the MBean to perform any operations it needs before being |
|
209 * unregistered by the MBean server. |
|
210 * @throws Exception This exception will be caught by the MBean server and |
|
211 * re-thrown as an MBeanRegistrationException. |
|
212 */ |
|
213 public void preDeregister() throws Exception { |
|
214 //TODO preDeregister implementation; |
|
215 } |
|
216 |
|
217 /** |
|
218 * Allows the MBean to perform any operations needed after having been |
|
219 * unregistered in the MBean server. |
|
220 */ |
|
221 public void postDeregister() { |
|
222 //TODO postDeregister implementation; |
|
223 } |
|
224 |
|
225 public String mount(JMXServiceURL url, |
|
226 String targetPath, |
|
227 Map<String,Object> optionsMap) |
|
228 throws IOException { |
|
229 return mount(url, targetPath, "", optionsMap); |
|
230 } |
|
231 |
|
232 // see NamespaceControllerMBean |
|
233 public String mount(JMXServiceURL url, |
|
234 String targetPath, |
|
235 String sourcePath, |
|
236 Map<String,Object> optionsMap) |
|
237 throws IOException { |
|
238 |
|
239 // TODO: handle description. |
|
240 final String dirName = |
|
241 JMXNamespaces.normalizeNamespaceName(targetPath); |
|
242 |
|
243 try { |
|
244 final ObjectInstance moi = |
|
245 JMXRemoteTargetNamespace.createNamespace(mbeanServer, |
|
246 dirName,url,optionsMap, |
|
247 JMXNamespaces.normalizeNamespaceName(sourcePath) |
|
248 ); |
|
249 final ObjectName nsMBean = moi.getObjectName(); |
|
250 try { |
|
251 mbeanServer.invoke(nsMBean, "connect", null,null); |
|
252 } catch (Throwable t) { |
|
253 mbeanServer.unregisterMBean(nsMBean); |
|
254 throw t; |
|
255 } |
|
256 return getMountPointID(nsMBean); |
|
257 } catch (InstanceAlreadyExistsException x) { |
|
258 throw new IllegalArgumentException(targetPath,x); |
|
259 } catch (IOException x) { |
|
260 throw x; |
|
261 } catch (Throwable x) { |
|
262 if (x instanceof Error) throw (Error)x; |
|
263 Throwable cause = x.getCause(); |
|
264 if (cause instanceof IOException) |
|
265 throw ((IOException)cause); |
|
266 if (cause == null) cause = x; |
|
267 |
|
268 final IOException io = |
|
269 new IOException("connect failed: "+cause); |
|
270 io.initCause(cause); |
|
271 throw io; |
|
272 } |
|
273 } |
|
274 |
|
275 private String getMountPointID(ObjectName dirName) { |
|
276 return dirName.toString(); |
|
277 } |
|
278 |
|
279 private ObjectName getHandlerName(String mountPointID) { |
|
280 try { |
|
281 final ObjectName tryit = ObjectName.getInstance(mountPointID); |
|
282 final ObjectName formatted = |
|
283 JMXNamespaces.getNamespaceObjectName(tryit.getDomain()); |
|
284 if (!formatted.equals(tryit)) |
|
285 throw new IllegalArgumentException(mountPointID+ |
|
286 ": invalid mountPointID"); |
|
287 return formatted; |
|
288 } catch (MalformedObjectNameException x) { |
|
289 throw new IllegalArgumentException(mountPointID,x); |
|
290 } |
|
291 } |
|
292 |
|
293 public boolean unmount(String mountPointID) |
|
294 throws IOException { |
|
295 final ObjectName dirName = getHandlerName(mountPointID); |
|
296 if (!mbeanServer.isRegistered(dirName)) |
|
297 throw new IllegalArgumentException(mountPointID+ |
|
298 ": no such name space"); |
|
299 final JMXRemoteNamespaceMBean mbean = |
|
300 JMX.newMBeanProxy(mbeanServer,dirName, |
|
301 JMXRemoteNamespaceMBean.class); |
|
302 try { |
|
303 mbean.close(); |
|
304 } catch (IOException io) { |
|
305 LOG.fine("Failed to close properly - ignoring exception: "+io); |
|
306 LOG.log(Level.FINEST, |
|
307 "Failed to close properly - ignoring exception",io); |
|
308 } finally { |
|
309 try { |
|
310 mbeanServer.unregisterMBean(dirName); |
|
311 } catch (InstanceNotFoundException x) { |
|
312 throw new IllegalArgumentException(mountPointID+ |
|
313 ": no such name space", x); |
|
314 } catch (MBeanRegistrationException x) { |
|
315 final IOException io = |
|
316 new IOException(mountPointID +": failed to unmount"); |
|
317 io.initCause(x); |
|
318 throw io; |
|
319 } |
|
320 } |
|
321 return true; |
|
322 } |
|
323 |
|
324 public boolean ismounted(String targetPath) { |
|
325 return mbeanServer.isRegistered(JMXNamespaces.getNamespaceObjectName(targetPath)); |
|
326 } |
|
327 |
|
328 public ObjectName getHandlerNameFor(String targetPath) { |
|
329 return JMXNamespaces.getNamespaceObjectName(targetPath); |
|
330 } |
|
331 |
|
332 public String[] findNamespaces() { |
|
333 return findNamespaces(null,null,0); |
|
334 } |
|
335 |
|
336 |
|
337 private ObjectName getDirPattern(String from) { |
|
338 try { |
|
339 if (from == null) |
|
340 return ObjectName.getInstance(ALL_NAMESPACES); |
|
341 final String namespace = |
|
342 ObjectNameRouter.normalizeNamespacePath(from,false,true,false); |
|
343 if (namespace.equals("")) |
|
344 return ObjectName.getInstance(ALL_NAMESPACES); |
|
345 if (JMXNamespaces.getNamespaceObjectName(namespace).isDomainPattern()) |
|
346 throw new IllegalArgumentException(from); |
|
347 return ObjectName.getInstance(namespace+NAMESPACE_SEPARATOR+ALL_NAMESPACES); |
|
348 } catch (MalformedObjectNameException x) { |
|
349 throw new IllegalArgumentException(from,x); |
|
350 } |
|
351 } |
|
352 |
|
353 public String[] findNamespaces(String from, String regex, int depth) { |
|
354 if (depth < 0) return new String[0]; |
|
355 final Set<String> res = new TreeSet<String>(); |
|
356 final ObjectName all = getDirPattern(from); |
|
357 Set<ObjectName> names = mbeanServer.queryNames(all,null); |
|
358 for (ObjectName dirName : names) { |
|
359 final String dir = dirName.getDomain(); |
|
360 if (regex == null || dir.matches(regex)) |
|
361 res.add(dir); |
|
362 if (depth > 0) |
|
363 res.addAll(Arrays.asList(findNamespaces(dir,regex,depth-1))); |
|
364 } |
|
365 return res.toArray(new String[res.size()]); |
|
366 } |
|
367 |
|
368 /** |
|
369 * Creates a {@link NamespaceController} MBean in the provided |
|
370 * {@link MBeanServerConnection}. |
|
371 * <p>The name of the MBean is that returned by {@link #preRegister} |
|
372 * as described by {@link #getObjectName}. |
|
373 * @throws IOException if an {@code IOException} is raised when invoking |
|
374 * the provided connection. |
|
375 * @throws InstanceAlreadyExistsException if an MBean was already |
|
376 * registered with the NamespaceController's name. |
|
377 * @throws MBeanRegistrationException if thrown by {@link |
|
378 * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName) |
|
379 * server.createMBean} |
|
380 * @throws MBeanException if thrown by {@link |
|
381 * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName) |
|
382 * server.createMBean} |
|
383 * @return the {@link ObjectInstance}, as returned by {@link |
|
384 * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName) |
|
385 * server.createMBean} |
|
386 **/ |
|
387 public static ObjectInstance createInstance(MBeanServerConnection server) |
|
388 throws IOException, InstanceAlreadyExistsException, |
|
389 MBeanRegistrationException, MBeanException { |
|
390 try { |
|
391 final ObjectInstance instance = |
|
392 server.createMBean(NamespaceController.class.getName(), null); |
|
393 return instance; |
|
394 } catch (NotCompliantMBeanException ex) { |
|
395 throw new RuntimeException("unexpected exception: " + ex, ex); |
|
396 } catch (ReflectionException ex) { |
|
397 throw new RuntimeException("unexpected exception: " + ex, ex); |
|
398 } |
|
399 } |
|
400 |
|
401 private final static String ALL_NAMESPACES= |
|
402 "*"+NAMESPACE_SEPARATOR+":"+ |
|
403 JMXNamespace.TYPE_ASSIGNMENT; |
|
404 |
|
405 } |