blob: ff5c5d85406dfaf0161011213529a24033ad0ed1 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2006 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
26package com.sun.jmx.remote.internal;
27
28import com.sun.jmx.remote.security.NotificationAccessController;
29import com.sun.jmx.remote.util.ClassLogger;
30import com.sun.jmx.remote.util.EnvHelp;
31import java.io.IOException;
32import java.security.AccessControlContext;
33import java.security.AccessController;
34import java.security.PrivilegedActionException;
35import java.security.PrivilegedExceptionAction;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44import javax.management.InstanceNotFoundException;
45import javax.management.ListenerNotFoundException;
46import javax.management.MBeanPermission;
47import javax.management.MBeanServer;
48import javax.management.Notification;
49import javax.management.NotificationBroadcaster;
50import javax.management.NotificationFilter;
51import javax.management.ObjectInstance;
52import javax.management.ObjectName;
53import javax.management.remote.NotificationResult;
54import javax.management.remote.TargetedNotification;
55import javax.management.MalformedObjectNameException;
56import javax.security.auth.Subject;
57
58public class ServerNotifForwarder {
59
60 public ServerNotifForwarder(MBeanServer mbeanServer,
61 Map env,
62 NotificationBuffer notifBuffer,
63 String connectionId) {
64 this.mbeanServer = mbeanServer;
65 this.notifBuffer = notifBuffer;
66 this.connectionId = connectionId;
67 connectionTimeout = EnvHelp.getServerConnectionTimeout(env);
68 checkNotificationEmission = EnvHelp.computeBooleanFromString(
69 env,
70 "jmx.remote.x.check.notification.emission");
71 notificationAccessController = (NotificationAccessController)
72 env.get("com.sun.jmx.remote.notification.access.controller");
73 }
74
75 public Integer addNotificationListener(final ObjectName name,
76 final NotificationFilter filter)
77 throws InstanceNotFoundException, IOException {
78
79 if (logger.traceOn()) {
80 logger.trace("addNotificationListener",
81 "Add a listener at " + name);
82 }
83
84 checkState();
85
86 // Explicitly check MBeanPermission for addNotificationListener
87 //
88 checkMBeanPermission(name, "addNotificationListener");
89 if (notificationAccessController != null) {
90 notificationAccessController.addNotificationListener(
91 connectionId,
92 name,
93 Subject.getSubject(AccessController.getContext()));
94 }
95 try {
96 boolean instanceOf =
97 AccessController.doPrivileged(
98 new PrivilegedExceptionAction<Boolean>() {
99 public Boolean run() throws InstanceNotFoundException {
100 return mbeanServer.isInstanceOf(name, broadcasterClass);
101 }
102 });
103 if (!instanceOf) {
104 throw new IllegalArgumentException("The specified MBean [" +
105 name + "] is not a " +
106 "NotificationBroadcaster " +
107 "object.");
108 }
109 } catch (PrivilegedActionException e) {
110 throw (InstanceNotFoundException) extractException(e);
111 }
112
113 final Integer id = getListenerID();
114
115 // 6238731: set the default domain if no domain is set.
116 ObjectName nn = name;
117 if (name.getDomain() == null || name.getDomain().equals("")) {
118 try {
119 nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(),
120 name.getKeyPropertyList());
121 } catch (MalformedObjectNameException mfoe) {
122 // impossible, but...
123 IOException ioe = new IOException(mfoe.getMessage());
124 ioe.initCause(mfoe);
125 throw ioe;
126 }
127 }
128
129 synchronized (listenerMap) {
130 IdAndFilter idaf = new IdAndFilter(id, filter);
131 Set<IdAndFilter> set = listenerMap.get(nn);
132 // Tread carefully because if set.size() == 1 it may be the
133 // Collections.singleton we make here, which is unmodifiable.
134 if (set == null)
135 set = Collections.singleton(idaf);
136 else {
137 if (set.size() == 1)
138 set = new HashSet<IdAndFilter>(set);
139 set.add(idaf);
140 }
141 listenerMap.put(nn, set);
142 }
143
144 return id;
145 }
146
147 public void removeNotificationListener(ObjectName name,
148 Integer[] listenerIDs)
149 throws Exception {
150
151 if (logger.traceOn()) {
152 logger.trace("removeNotificationListener",
153 "Remove some listeners from " + name);
154 }
155
156 checkState();
157
158 // Explicitly check MBeanPermission for removeNotificationListener
159 //
160 checkMBeanPermission(name, "removeNotificationListener");
161 if (notificationAccessController != null) {
162 notificationAccessController.removeNotificationListener(
163 connectionId,
164 name,
165 Subject.getSubject(AccessController.getContext()));
166 }
167
168 Exception re = null;
169 for (int i = 0 ; i < listenerIDs.length ; i++) {
170 try {
171 removeNotificationListener(name, listenerIDs[i]);
172 } catch (Exception e) {
173 // Give back the first exception
174 //
175 if (re != null) {
176 re = e;
177 }
178 }
179 }
180 if (re != null) {
181 throw re;
182 }
183 }
184
185 public void removeNotificationListener(ObjectName name, Integer listenerID)
186 throws
187 InstanceNotFoundException,
188 ListenerNotFoundException,
189 IOException {
190
191 if (logger.traceOn()) {
192 logger.trace("removeNotificationListener",
193 "Remove the listener " + listenerID + " from " + name);
194 }
195
196 checkState();
197
198 if (name != null && !name.isPattern()) {
199 if (!mbeanServer.isRegistered(name)) {
200 throw new InstanceNotFoundException("The MBean " + name +
201 " is not registered.");
202 }
203 }
204
205 synchronized (listenerMap) {
206 // Tread carefully because if set.size() == 1 it may be a
207 // Collections.singleton, which is unmodifiable.
208 Set<IdAndFilter> set = listenerMap.get(name);
209 IdAndFilter idaf = new IdAndFilter(listenerID, null);
210 if (set == null || !set.contains(idaf))
211 throw new ListenerNotFoundException("Listener not found");
212 if (set.size() == 1)
213 listenerMap.remove(name);
214 else
215 set.remove(idaf);
216 }
217 }
218
219 /* This is the object that will apply our filtering to candidate
220 * notifications. First of all, if there are no listeners for the
221 * ObjectName that the notification is coming from, we go no further.
222 * Then, for each listener, we must apply the corresponding filter (if any)
223 * and ignore the listener if the filter rejects. Finally, we apply
224 * some access checks which may also reject the listener.
225 *
226 * A given notification may trigger several listeners on the same MBean,
227 * which is why listenerMap is a Map<ObjectName, Set<IdAndFilter>> and
228 * why we add the found notifications to a supplied List rather than
229 * just returning a boolean.
230 */
231 private final NotificationBufferFilter bufferFilter =
232 new NotificationBufferFilter() {
233 public void apply(List<TargetedNotification> targetedNotifs,
234 ObjectName source, Notification notif) {
235 // We proceed in two stages here, to avoid holding the listenerMap
236 // lock while invoking the filters (which are user code).
237 final IdAndFilter[] candidates;
238 synchronized (listenerMap) {
239 final Set<IdAndFilter> set = listenerMap.get(source);
240 if (set == null) {
241 logger.debug("bufferFilter", "no listeners for this name");
242 return;
243 }
244 candidates = new IdAndFilter[set.size()];
245 set.toArray(candidates);
246 }
247 // We don't synchronize on targetedNotifs, because it is a local
248 // variable of our caller and no other thread can see it.
249 for (IdAndFilter idaf : candidates) {
250 final NotificationFilter nf = idaf.getFilter();
251 if (nf == null || nf.isNotificationEnabled(notif)) {
252 logger.debug("bufferFilter", "filter matches");
253 final TargetedNotification tn =
254 new TargetedNotification(notif, idaf.getId());
255 if (allowNotificationEmission(source, tn))
256 targetedNotifs.add(tn);
257 }
258 }
259 }
260 };
261
262 public NotificationResult fetchNotifs(long startSequenceNumber,
263 long timeout,
264 int maxNotifications) {
265 if (logger.traceOn()) {
266 logger.trace("fetchNotifs", "Fetching notifications, the " +
267 "startSequenceNumber is " + startSequenceNumber +
268 ", the timeout is " + timeout +
269 ", the maxNotifications is " + maxNotifications);
270 }
271
272 NotificationResult nr = null;
273 final long t = Math.min(connectionTimeout, timeout);
274 try {
275 nr = notifBuffer.fetchNotifications(bufferFilter,
276 startSequenceNumber,
277 t, maxNotifications);
278 } catch (InterruptedException ire) {
279 nr = new NotificationResult(0L, 0L, new TargetedNotification[0]);
280 }
281
282 if (logger.traceOn()) {
283 logger.trace("fetchNotifs", "Forwarding the notifs: "+nr);
284 }
285
286 return nr;
287 }
288
289 public void terminate() {
290 if (logger.traceOn()) {
291 logger.trace("terminate", "Be called.");
292 }
293
294 synchronized(terminationLock) {
295 if (terminated) {
296 return;
297 }
298
299 terminated = true;
300
301 synchronized(listenerMap) {
302 listenerMap.clear();
303 }
304 }
305
306 if (logger.traceOn()) {
307 logger.trace("terminate", "Terminated.");
308 }
309 }
310
311 //----------------
312 // PRIVATE METHODS
313 //----------------
314
315 private void checkState() throws IOException {
316 synchronized(terminationLock) {
317 if (terminated) {
318 throw new IOException("The connection has been terminated.");
319 }
320 }
321 }
322
323 private Integer getListenerID() {
324 synchronized(listenerCounterLock) {
325 return new Integer(listenerCounter++);
326 }
327 }
328
329 /**
330 * Explicitly check the MBeanPermission for
331 * the current access control context.
332 */
333 private void checkMBeanPermission(final ObjectName name,
334 final String actions)
335 throws InstanceNotFoundException, SecurityException {
336 SecurityManager sm = System.getSecurityManager();
337 if (sm != null) {
338 AccessControlContext acc = AccessController.getContext();
339 ObjectInstance oi = null;
340 try {
341 oi = AccessController.doPrivileged(
342 new PrivilegedExceptionAction<ObjectInstance>() {
343 public ObjectInstance run()
344 throws InstanceNotFoundException {
345 return mbeanServer.getObjectInstance(name);
346 }
347 });
348 } catch (PrivilegedActionException e) {
349 throw (InstanceNotFoundException) extractException(e);
350 }
351 String classname = oi.getClassName();
352 MBeanPermission perm = new MBeanPermission(classname,
353 null,
354 name,
355 actions);
356 sm.checkPermission(perm, acc);
357 }
358 }
359
360 /**
361 * Check if the caller has the right to get the following notifications.
362 */
363 private boolean allowNotificationEmission(ObjectName name,
364 TargetedNotification tn) {
365 try {
366 if (checkNotificationEmission) {
367 checkMBeanPermission(name, "addNotificationListener");
368 }
369 if (notificationAccessController != null) {
370 notificationAccessController.fetchNotification(
371 connectionId,
372 name,
373 tn.getNotification(),
374 Subject.getSubject(AccessController.getContext()));
375 }
376 return true;
377 } catch (SecurityException e) {
378 if (logger.debugOn()) {
379 logger.debug("fetchNotifs", "Notification " +
380 tn.getNotification() + " not forwarded: the " +
381 "caller didn't have the required access rights");
382 }
383 return false;
384 } catch (Exception e) {
385 if (logger.debugOn()) {
386 logger.debug("fetchNotifs", "Notification " +
387 tn.getNotification() + " not forwarded: " +
388 "got an unexpected exception: " + e);
389 }
390 return false;
391 }
392 }
393
394 /**
395 * Iterate until we extract the real exception
396 * from a stack of PrivilegedActionExceptions.
397 */
398 private static Exception extractException(Exception e) {
399 while (e instanceof PrivilegedActionException) {
400 e = ((PrivilegedActionException)e).getException();
401 }
402 return e;
403 }
404
405 private static class IdAndFilter {
406 private Integer id;
407 private NotificationFilter filter;
408
409 IdAndFilter(Integer id, NotificationFilter filter) {
410 this.id = id;
411 this.filter = filter;
412 }
413
414 Integer getId() {
415 return this.id;
416 }
417
418 NotificationFilter getFilter() {
419 return this.filter;
420 }
421
422 public int hashCode() {
423 return id.hashCode();
424 }
425
426 public boolean equals(Object o) {
427 return ((o instanceof IdAndFilter) &&
428 ((IdAndFilter) o).getId().equals(getId()));
429 }
430 }
431
432 //------------------
433 // PRIVATE VARIABLES
434 //------------------
435
436 private MBeanServer mbeanServer;
437
438 private final String connectionId;
439
440 private final long connectionTimeout;
441
442 private static int listenerCounter = 0;
443 private final static int[] listenerCounterLock = new int[0];
444
445 private NotificationBuffer notifBuffer;
446 private Map<ObjectName, Set<IdAndFilter>> listenerMap =
447 new HashMap<ObjectName, Set<IdAndFilter>>();
448
449 private boolean terminated = false;
450 private final int[] terminationLock = new int[0];
451
452 static final String broadcasterClass =
453 NotificationBroadcaster.class.getName();
454
455 private final boolean checkNotificationEmission;
456
457 private final NotificationAccessController notificationAccessController;
458
459 private static final ClassLogger logger =
460 new ClassLogger("javax.management.remote.misc", "ServerNotifForwarder");
461}