1/*
2 * Copyright (c) 2003, 2015, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 7654321
27 * @summary Tests the NotificationBuffer class.
28 * @author Eamonn McManus
29 * @modules java.management/com.sun.jmx.remote.internal
30 *          java.management/com.sun.jmx.remote.util
31 * @run clean NotificationBufferTest
32 * @run build NotificationBufferTest NotificationSender NotificationSenderMBean
33 * @run main NotificationBufferTest
34 */
35
36import java.util.Arrays;
37import java.util.Collections;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Set;
41import java.util.HashMap;
42
43import javax.management.MBeanServer;
44import javax.management.MBeanServerFactory;
45import javax.management.MBeanServerInvocationHandler;
46import javax.management.MBeanServerNotification;
47import javax.management.Notification;
48import javax.management.NotificationFilter;
49import javax.management.NotificationFilterSupport;
50import javax.management.ObjectName;
51import javax.management.loading.MLet;
52
53import javax.management.remote.NotificationResult;
54import javax.management.remote.TargetedNotification;
55
56import com.sun.jmx.remote.internal.ArrayNotificationBuffer;
57import com.sun.jmx.remote.internal.NotificationBufferFilter;
58import com.sun.jmx.remote.internal.NotificationBuffer;
59
60public class NotificationBufferTest {
61
62    public static void main(String[] args) {
63//      System.setProperty("java.util.logging.config.file",
64//                         "../../../../logging.properties");
65//      // we are in <workspace>/build/test/JTwork/scratch
66        try {
67//          java.util.logging.LogManager.getLogManager().readConfiguration();
68            boolean ok = test();
69            if (ok) {
70                System.out.println("Test completed");
71                return;
72            } else {
73                System.out.println("Test failed!");
74                System.exit(1);
75            }
76        } catch (Exception e) {
77            System.err.println("Unexpected exception: " + e);
78            e.printStackTrace();
79            System.exit(1);
80        }
81    }
82
83    private static boolean test() throws Exception {
84        MBeanServer mbs = MBeanServerFactory.createMBeanServer();
85
86        Integer queuesize = new Integer(10);
87        HashMap env = new HashMap();
88        env.put(com.sun.jmx.remote.util.EnvHelp.BUFFER_SIZE_PROPERTY, queuesize);
89        final NotificationBuffer nb =
90            ArrayNotificationBuffer.getNotificationBuffer(mbs, env);
91
92        final ObjectName senderName = new ObjectName("dom:type=sender");
93        final ObjectName wildcardName = new ObjectName("*:*");
94        final String notifType =
95            MBeanServerNotification.REGISTRATION_NOTIFICATION;
96
97        Integer allListenerId = new Integer(99);
98        NotificationBufferFilter allListenerFilter =
99                makeFilter(allListenerId, wildcardName, null);
100        NotificationFilterSupport regFilter = new NotificationFilterSupport();
101        regFilter.enableType(notifType);
102
103        // Get initial sequence number
104        NotificationResult nr =
105            nb.fetchNotifications(allListenerFilter, 0, 0L, 0);
106        int nnotifs = nr.getTargetedNotifications().length;
107        if (nnotifs > 0) {
108            System.out.println("Expected 0 notifs for initial fetch, " +
109                               "got " + nnotifs);
110            return false;
111        }
112        System.out.println("Got 0 notifs for initial fetch, OK");
113
114        long earliest = nr.getEarliestSequenceNumber();
115        long next = nr.getNextSequenceNumber();
116        if (earliest != next) {
117            System.out.println("Expected earliest==next in initial fetch, " +
118                               "earliest=" + earliest + "; next=" + next);
119            return false;
120        }
121        System.out.println("Got earliest==next in initial fetch, OK");
122
123        mbs.createMBean(MLet.class.getName(), null);
124        mbs.createMBean(NotificationSender.class.getName(), senderName);
125
126        NotificationSenderMBean sender = (NotificationSenderMBean)
127            MBeanServerInvocationHandler.newProxyInstance(mbs,
128                                                          senderName,
129                                                          NotificationSenderMBean.class,
130                                                          false);
131
132        /* We test here that MBeans already present when the
133           NotificationBuffer was created get a listener for the
134           buffer, as do MBeans created later.  The
135           MBeanServerDelegate was already present, while the
136           NotificationSender was created later.  */
137
138        // Check that the NotificationSender does indeed have a listener
139        /* Note we are dependent on the specifics of our JMX
140           implementation here.  There is no guarantee that the MBean
141           creation listeners will have run to completion when
142           creation of the MBean returns.  */
143        int nlisteners = sender.getListenerCount();
144        if (nlisteners != 1) {
145            System.out.println("Notification sender should have 1 listener, " +
146                               "has " + nlisteners);
147            return false;
148        }
149        System.out.println("Notification sender has 1 listener, OK");
150
151        // Now we should see two creation notifications
152        nr = nb.fetchNotifications(allListenerFilter, next, 0L,
153                                   Integer.MAX_VALUE);
154        TargetedNotification[] tns = nr.getTargetedNotifications();
155        if (tns.length != 2) {
156            System.out.println("Expected 2 notifs, got: " +
157                               Arrays.asList(tns));
158            return false;
159        }
160        if (!(tns[0].getNotification() instanceof MBeanServerNotification)
161            || !(tns[1].getNotification() instanceof MBeanServerNotification))
162            {
163            System.out.println("Expected 2 MBeanServerNotifications, got: " +
164                               Arrays.asList(tns));
165            return false;
166        }
167        if (!tns[0].getListenerID().equals(tns[1].getListenerID())
168            || !tns[0].getListenerID().equals(allListenerId)) {
169            System.out.println("Bad listener IDs: " + Arrays.asList(tns));
170            return false;
171        }
172        System.out.println("Got 2 different MBeanServerNotifications, OK");
173
174        // If we ask for max 1 notifs, we should only get one
175        nr = nb.fetchNotifications(allListenerFilter, next, 0L, 1);
176        tns = nr.getTargetedNotifications();
177        if (tns.length != 1) {
178            System.out.println("Expected 1 notif, got: " + Arrays.asList(tns));
179            return false;
180        }
181        TargetedNotification tn1 = tns[0];
182        System.out.println("Got 1 notif when asked for 1, OK");
183
184        // Now we should get the other one
185        nr = nb.fetchNotifications(allListenerFilter, nr.getNextSequenceNumber(),
186                                   0L, 1);
187        tns = nr.getTargetedNotifications();
188        if (tns.length != 1) {
189            System.out.println("Expected 1 notif, got: " + Arrays.asList(tns));
190            return false;
191        }
192        TargetedNotification tn2 = tns[0];
193        System.out.println("Got 1 notif when asked for 1 again, OK");
194
195        if (tn1.getNotification() == tn2.getNotification()) {
196            System.out.println("Returned same notif twice: " + tn1);
197            return false;
198        }
199        System.out.println("2 creation notifs are different, OK");
200
201        // Now we should get none (timeout is 0)
202        long oldNext = nr.getNextSequenceNumber();
203        nr = nb.fetchNotifications(allListenerFilter, oldNext, 0L,
204                                   Integer.MAX_VALUE);
205        tns = nr.getTargetedNotifications();
206        if (tns.length != 0) {
207            System.out.println("Expected 0 notifs, got: " +
208                               Arrays.asList(tns));
209            return false;
210        }
211        System.out.println("Got 0 notifs with 0 timeout, OK");
212        if (nr.getNextSequenceNumber() != oldNext) {
213            System.out.println("Sequence number changed: " + oldNext + " -> " +
214                               nr.getNextSequenceNumber());
215            return false;
216        }
217        System.out.println("Next seqno unchanged with 0 timeout, OK");
218
219        // Check that timeouts work
220        long startTime = System.currentTimeMillis();
221        nr = nb.fetchNotifications(allListenerFilter, oldNext, 250L,
222                                   Integer.MAX_VALUE);
223        tns = nr.getTargetedNotifications();
224        if (tns.length != 0) {
225            System.out.println("Expected 0 notifs, got: " +
226                               Arrays.asList(tns));
227            return false;
228        }
229        long endTime = System.currentTimeMillis();
230        long elapsed = endTime - startTime;
231        if (elapsed < 250L) {
232            System.out.println("Elapsed time shorter than timeout: " +
233                               elapsed);
234            return false;
235        }
236        System.out.println("Timeout worked, OK");
237
238        // Check that notification filtering works
239        NotificationFilter senderFilter = new NotificationFilter() {
240            public boolean isNotificationEnabled(Notification n) {
241                if (!(n instanceof MBeanServerNotification))
242                    return false;
243                MBeanServerNotification mbsn = (MBeanServerNotification) n;
244                return (mbsn.getMBeanName().equals(senderName));
245            }
246        };
247        Integer senderListenerId = new Integer(88);
248        NotificationBufferFilter senderListenerFilter =
249                makeFilter(senderListenerId, wildcardName, senderFilter);
250        nr = nb.fetchNotifications(senderListenerFilter, 0, 1000L,
251                                   Integer.MAX_VALUE);
252        tns = nr.getTargetedNotifications();
253        if (tns.length != 1) {
254            System.out.println("Expected 1 notif, got: " + Arrays.asList(tns));
255            return false;
256        }
257        MBeanServerNotification mbsn =
258            (MBeanServerNotification) tns[0].getNotification();
259        if (!mbsn.getMBeanName().equals(senderName)) {
260            System.out.println("Expected notif with senderName, got: " +
261                               mbsn + " (" + mbsn.getMBeanName() + ")");
262            return false;
263        }
264        System.out.println("Successfully applied NotificationFilter, OK");
265
266        // Now send 8 notifs to fill up our 10-element buffer
267        sender.sendNotifs("tiddly.pom", 8);
268        nr = nb.fetchNotifications(allListenerFilter, 0, 1000L,
269                                   Integer.MAX_VALUE);
270        tns = nr.getTargetedNotifications();
271        if (tns.length != 10) {
272            System.out.println("Expected 10 notifs, got: " +
273                               Arrays.asList(tns));
274            return false;
275        }
276        System.out.println("Got full buffer of 10 notifications, OK");
277
278        // Check that the 10 notifs are the ones we expected
279        for (int i = 0; i < 10; i++) {
280            String expected =
281                (i < 2) ? notifType : "tiddly.pom";
282            String found = tns[i].getNotification().getType();
283            if (!found.equals(expected)) {
284                System.out.println("Notif " + i + " bad type: expected <" +
285                                   expected + ">, found <" + found + ">");
286                return false;
287            }
288        }
289        System.out.println("Notifs have right types, OK");
290
291        // Check that ObjectName filtering works
292        NotificationBufferFilter senderNameFilter =
293                makeFilter(new Integer(66), senderName, null);
294        nr = nb.fetchNotifications(senderNameFilter, 0, 0L,
295                                   Integer.MAX_VALUE);
296        tns = nr.getTargetedNotifications();
297        if (tns.length != 8) {
298            System.out.println("Bad result from ObjectName filtering: " +
299                               Arrays.asList(tns));
300            return false;
301        }
302        System.out.println("ObjectName filtering works, OK");
303
304        // Send one more notif, which should cause the oldest one to drop
305        sender.sendNotifs("foo.bar", 1);
306        nr = nb.fetchNotifications(allListenerFilter, 0, 1000L,
307                                   Integer.MAX_VALUE);
308        if (nr.getEarliestSequenceNumber() <= earliest) {
309            System.out.println("Expected earliest to increase: " +
310                               nr.getEarliestSequenceNumber() + " should be > "
311                               + earliest);
312            return false;
313        }
314        System.out.println("Earliest notif dropped, OK");
315
316        // Check that the 10 notifs are the ones we expected
317        tns = nr.getTargetedNotifications();
318        for (int i = 0; i < 10; i++) {
319            String expected =
320                (i < 1) ? notifType
321                        : (i < 9) ? "tiddly.pom" : "foo.bar";
322            String found = tns[i].getNotification().getType();
323            if (!found.equals(expected)) {
324                System.out.println("Notif " + i + " bad type: expected <" +
325                                   expected + ">, found <" + found + ">");
326                return false;
327            }
328        }
329        System.out.println("Notifs have right types, OK");
330
331        // Apply a filter that only selects the first notif, with max notifs 1,
332        // then check that it skipped past the others even though it already
333        // had its 1 notif
334        NotificationBufferFilter firstFilter =
335                makeFilter(new Integer(55), wildcardName, regFilter);
336        nr = nb.fetchNotifications(firstFilter, 0, 1000L, 1);
337        tns = nr.getTargetedNotifications();
338        if (tns.length != 1
339            || !tns[0].getNotification().getType().equals(notifType)) {
340            System.out.println("Unexpected return from filtered call: " +
341                               Arrays.asList(tns));
342            return false;
343        }
344        nr = nb.fetchNotifications(allListenerFilter, nr.getNextSequenceNumber(),
345                                   0L, 1000);
346        tns = nr.getTargetedNotifications();
347        if (tns.length != 0) {
348            System.out.println("Expected 0 notifs, got: " +
349                               Arrays.asList(tns));
350            return false;
351        }
352
353        // Create a second, larger buffer, which should share the same notifs
354        nr = nb.fetchNotifications(allListenerFilter, 0,
355                                   1000L, Integer.MAX_VALUE);
356        queuesize = new Integer(20);
357        env.put(com.sun.jmx.remote.util.EnvHelp.BUFFER_SIZE_PROPERTY, queuesize);
358        NotificationBuffer nb2 =
359            ArrayNotificationBuffer.getNotificationBuffer(mbs, env);
360        NotificationResult nr2 =
361            nb2.fetchNotifications(allListenerFilter, 0,
362                                   1000L, Integer.MAX_VALUE);
363        if (nr.getEarliestSequenceNumber() != nr2.getEarliestSequenceNumber()
364            || nr.getNextSequenceNumber() != nr2.getNextSequenceNumber()
365            || !sameTargetedNotifs(nr.getTargetedNotifications(),
366                                   nr2.getTargetedNotifications()))
367            return false;
368        System.out.println("Adding second buffer preserved notif list, OK");
369
370        // Check that the capacity is now 20
371        sender.sendNotifs("propter.hoc", 10);
372        nr2 = nb2.fetchNotifications(allListenerFilter, 0,
373                                     1000L, Integer.MAX_VALUE);
374        if (nr.getEarliestSequenceNumber() !=
375            nr2.getEarliestSequenceNumber()) {
376            System.out.println("Earliest seq number changed after notifs " +
377                               "that should have fit");
378            return false;
379        }
380        TargetedNotification[] tns2 = new TargetedNotification[10];
381        Arrays.asList(nr2.getTargetedNotifications()).subList(0, 10).toArray(tns2);
382        if (!sameTargetedNotifs(nr.getTargetedNotifications(), tns2)) {
383            System.out.println("Early notifs changed after notifs " +
384                               "that should have fit");
385            return false;
386        }
387        System.out.println("New notifications fit in now-larger buffer, OK");
388
389        // Drop the second buffer and check that the capacity shrinks
390        nb2.dispose();
391        NotificationResult nr3 =
392            nb.fetchNotifications(allListenerFilter, 0,
393                                  1000L, Integer.MAX_VALUE);
394        if (nr3.getEarliestSequenceNumber() != nr.getNextSequenceNumber()) {
395            System.out.println("After shrink, notifs not dropped as expected");
396            return false;
397        }
398        if (nr3.getNextSequenceNumber() != nr2.getNextSequenceNumber()) {
399            System.out.println("After shrink, next seq no does not match");
400            return false;
401        }
402        tns2 = new TargetedNotification[10];
403        Arrays.asList(nr2.getTargetedNotifications()).subList(10, 20).toArray(tns2);
404        if (!sameTargetedNotifs(nr3.getTargetedNotifications(), tns2)) {
405            System.out.println("Later notifs not preserved after shrink");
406            return false;
407        }
408        System.out.println("Dropping second buffer shrank capacity, OK");
409
410        // Final test: check that destroying the final shared buffer
411        // removes its listeners
412        nb.dispose();
413        nlisteners = sender.getListenerCount();
414        if (nlisteners != 0) {
415            System.out.println("Disposing buffer should leave 0 listeners, " +
416                               "but notification sender has " + nlisteners);
417            return false;
418        }
419        System.out.println("Dropping first buffer drops listeners, OK");
420
421        return true;
422    }
423
424    private static boolean sameTargetedNotifs(TargetedNotification[] tn1,
425                                              TargetedNotification[] tn2) {
426        if (tn1.length != tn2.length) {
427            System.out.println("Not same length");
428            return false;
429        }
430        for (int i = 0; i < tn1.length; i++) {
431            TargetedNotification n1 = tn1[i];
432            TargetedNotification n2 = tn2[i];
433            if (n1.getNotification() != n2.getNotification()
434                || !n1.getListenerID().equals(n2.getListenerID()))
435                return false;
436        }
437        return true;
438    }
439
440    private static NotificationBufferFilter makeFilter(final Integer id,
441                                                       final ObjectName pattern,
442                                                       final NotificationFilter filter) {
443        return new NotificationBufferFilter() {
444            public void apply(List<TargetedNotification> notifs,
445                              ObjectName source, Notification notif) {
446                if (pattern.apply(source)) {
447                    if (filter == null || filter.isNotificationEnabled(notif))
448                        notifs.add(new TargetedNotification(notif, id));
449                }
450            }
451        };
452    };
453}
454