Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/javax/management/remote/mandatory/loading/MissingClassTest.java
38867 views
1
/*
2
* Copyright (c) 2003, 2013, 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 4915825 4921009 4934965 4977469 8019584
27
* @summary Tests behavior when client or server gets object of unknown class
28
* @author Eamonn McManus
29
* @run clean MissingClassTest SingleClassLoader
30
* @run build MissingClassTest SingleClassLoader
31
* @run main MissingClassTest
32
* @key randomness
33
*/
34
35
/*
36
Tests that clients and servers react correctly when they receive
37
objects of unknown classes. This can happen easily due to version
38
skew or missing jar files on one end or the other. The default
39
behaviour of causing a connection to die because of the resultant
40
IOException is not acceptable! We try sending attributes and invoke
41
parameters to the server of classes it doesn't know, and we try
42
sending attributes, exceptions and notifications to the client of
43
classes it doesn't know.
44
45
We also test objects that are of known class but not serializable.
46
The test cases are similar.
47
*/
48
49
import java.io.ByteArrayOutputStream;
50
import java.io.IOException;
51
import java.io.NotSerializableException;
52
import java.io.ObjectOutputStream;
53
import java.net.MalformedURLException;
54
import java.util.HashMap;
55
import java.util.Map;
56
import java.util.Random;
57
import java.util.Set;
58
import javax.management.Attribute;
59
import javax.management.MBeanServer;
60
import javax.management.MBeanServerConnection;
61
import javax.management.MBeanServerFactory;
62
import javax.management.Notification;
63
import javax.management.NotificationBroadcasterSupport;
64
import javax.management.NotificationFilter;
65
import javax.management.NotificationListener;
66
import javax.management.ObjectName;
67
import javax.management.remote.JMXConnectionNotification;
68
import javax.management.remote.JMXConnector;
69
import javax.management.remote.JMXConnectorFactory;
70
import javax.management.remote.JMXConnectorServer;
71
import javax.management.remote.JMXConnectorServerFactory;
72
import javax.management.remote.JMXServiceURL;
73
import javax.management.remote.rmi.RMIConnectorServer;
74
75
public class MissingClassTest {
76
private static final int NNOTIFS = 50;
77
78
private static ClassLoader clientLoader, serverLoader;
79
private static Object serverUnknown;
80
private static Exception clientUnknown;
81
private static ObjectName on;
82
private static final Object[] NO_OBJECTS = new Object[0];
83
private static final String[] NO_STRINGS = new String[0];
84
85
private static final Object unserializableObject = Thread.currentThread();
86
87
private static boolean isInstance(Object o, String cn) {
88
try {
89
Class<?> c = Class.forName(cn);
90
return c.isInstance(o);
91
} catch (ClassNotFoundException x) {
92
return false;
93
}
94
}
95
96
public static void main(String[] args) throws Exception {
97
System.out.println("Test that the client or server end of a " +
98
"connection does not fail if sent an object " +
99
"it cannot deserialize");
100
101
on = new ObjectName("test:type=Test");
102
103
ClassLoader testLoader = MissingClassTest.class.getClassLoader();
104
clientLoader =
105
new SingleClassLoader("$ServerUnknown$", HashMap.class,
106
testLoader);
107
serverLoader =
108
new SingleClassLoader("$ClientUnknown$", Exception.class,
109
testLoader);
110
serverUnknown =
111
clientLoader.loadClass("$ServerUnknown$").newInstance();
112
clientUnknown = (Exception)
113
serverLoader.loadClass("$ClientUnknown$").newInstance();
114
115
final String[] protos = {"rmi", /*"iiop",*/ "jmxmp"};
116
boolean ok = true;
117
for (int i = 0; i < protos.length; i++) {
118
try {
119
ok &= test(protos[i]);
120
} catch (Exception e) {
121
System.out.println("TEST FAILED WITH EXCEPTION:");
122
e.printStackTrace(System.out);
123
ok = false;
124
}
125
}
126
127
if (ok)
128
System.out.println("Test passed");
129
else {
130
throw new RuntimeException("TEST FAILED");
131
}
132
}
133
134
private static boolean test(String proto) throws Exception {
135
System.out.println("Testing for proto " + proto);
136
137
boolean ok = true;
138
139
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
140
mbs.createMBean(Test.class.getName(), on);
141
142
JMXConnectorServer cs;
143
JMXServiceURL url = new JMXServiceURL(proto, null, 0);
144
Map<String,Object> serverMap = new HashMap<>();
145
serverMap.put(JMXConnectorServerFactory.DEFAULT_CLASS_LOADER,
146
serverLoader);
147
148
// make sure no auto-close at server side
149
serverMap.put("jmx.remote.x.server.connection.timeout", "888888888");
150
151
try {
152
cs = JMXConnectorServerFactory.newJMXConnectorServer(url,
153
serverMap,
154
mbs);
155
} catch (MalformedURLException e) {
156
System.out.println("System does not recognize URL: " + url +
157
"; ignoring");
158
return true;
159
}
160
cs.start();
161
JMXServiceURL addr = cs.getAddress();
162
Map<String,Object> clientMap = new HashMap<>();
163
clientMap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,
164
clientLoader);
165
166
System.out.println("Connecting for client-unknown test");
167
168
JMXConnector client = JMXConnectorFactory.connect(addr, clientMap);
169
170
// add a listener to verify no failed notif
171
CNListener cnListener = new CNListener();
172
client.addConnectionNotificationListener(cnListener, null, null);
173
174
MBeanServerConnection mbsc = client.getMBeanServerConnection();
175
176
System.out.println("Getting attribute with class unknown to client");
177
try {
178
Object result = mbsc.getAttribute(on, "ClientUnknown");
179
System.out.println("TEST FAILS: getAttribute for class " +
180
"unknown to client should fail, returned: " +
181
result);
182
ok = false;
183
} catch (IOException e) {
184
Throwable cause = e.getCause();
185
if (isInstance(cause, "org.omg.CORBA.MARSHAL")) // see CR 4935098
186
cause = cause.getCause();
187
if (cause instanceof ClassNotFoundException) {
188
System.out.println("Success: got an IOException wrapping " +
189
"a ClassNotFoundException");
190
} else {
191
System.out.println("TEST FAILS: Caught IOException (" + e +
192
") but cause should be " +
193
"ClassNotFoundException: " + cause);
194
ok = false;
195
}
196
}
197
198
System.out.println("Doing queryNames to ensure connection alive");
199
Set<ObjectName> names = mbsc.queryNames(null, null);
200
System.out.println("queryNames returned " + names);
201
202
System.out.println("Provoke exception of unknown class");
203
try {
204
mbsc.invoke(on, "throwClientUnknown", NO_OBJECTS, NO_STRINGS);
205
System.out.println("TEST FAILS: did not get exception");
206
ok = false;
207
} catch (IOException e) {
208
Throwable wrapped = e.getCause();
209
if (isInstance(wrapped, "org.omg.CORBA.MARSHAL")) // see CR 4935098
210
wrapped = wrapped.getCause();
211
if (wrapped instanceof ClassNotFoundException) {
212
System.out.println("Success: got an IOException wrapping " +
213
"a ClassNotFoundException: " +
214
wrapped);
215
} else {
216
System.out.println("TEST FAILS: Got IOException but cause " +
217
"should be ClassNotFoundException: ");
218
if (wrapped == null)
219
System.out.println("(null)");
220
else
221
wrapped.printStackTrace(System.out);
222
ok = false;
223
}
224
} catch (Exception e) {
225
System.out.println("TEST FAILS: Got wrong exception: " +
226
"should be IOException with cause " +
227
"ClassNotFoundException:");
228
e.printStackTrace(System.out);
229
ok = false;
230
}
231
232
System.out.println("Doing queryNames to ensure connection alive");
233
names = mbsc.queryNames(null, null);
234
System.out.println("queryNames returned " + names);
235
236
ok &= notifyTest(client, mbsc);
237
238
System.out.println("Doing queryNames to ensure connection alive");
239
names = mbsc.queryNames(null, null);
240
System.out.println("queryNames returned " + names);
241
242
for (int i = 0; i < 2; i++) {
243
boolean setAttribute = (i == 0); // else invoke
244
String what = setAttribute ? "setAttribute" : "invoke";
245
System.out.println("Trying " + what +
246
" with class unknown to server");
247
try {
248
if (setAttribute) {
249
mbsc.setAttribute(on, new Attribute("ServerUnknown",
250
serverUnknown));
251
} else {
252
mbsc.invoke(on, "useServerUnknown",
253
new Object[] {serverUnknown},
254
new String[] {"java.lang.Object"});
255
}
256
System.out.println("TEST FAILS: " + what + " with " +
257
"class unknown to server should fail " +
258
"but did not");
259
ok = false;
260
} catch (IOException e) {
261
Throwable cause = e.getCause();
262
if (isInstance(cause, "org.omg.CORBA.MARSHAL")) // see CR 4935098
263
cause = cause.getCause();
264
if (cause instanceof ClassNotFoundException) {
265
System.out.println("Success: got an IOException " +
266
"wrapping a ClassNotFoundException");
267
} else {
268
System.out.println("TEST FAILS: Caught IOException (" + e +
269
") but cause should be " +
270
"ClassNotFoundException: " + cause);
271
e.printStackTrace(System.out); // XXX
272
ok = false;
273
}
274
}
275
}
276
277
System.out.println("Doing queryNames to ensure connection alive");
278
names = mbsc.queryNames(null, null);
279
System.out.println("queryNames returned " + names);
280
281
System.out.println("Trying to get unserializable attribute");
282
try {
283
mbsc.getAttribute(on, "Unserializable");
284
System.out.println("TEST FAILS: get unserializable worked " +
285
"but should not");
286
ok = false;
287
} catch (IOException e) {
288
System.out.println("Success: got an IOException: " + e +
289
" (cause: " + e.getCause() + ")");
290
}
291
292
System.out.println("Doing queryNames to ensure connection alive");
293
names = mbsc.queryNames(null, null);
294
System.out.println("queryNames returned " + names);
295
296
System.out.println("Trying to set unserializable attribute");
297
try {
298
Attribute attr =
299
new Attribute("Unserializable", unserializableObject);
300
mbsc.setAttribute(on, attr);
301
System.out.println("TEST FAILS: set unserializable worked " +
302
"but should not");
303
ok = false;
304
} catch (IOException e) {
305
System.out.println("Success: got an IOException: " + e +
306
" (cause: " + e.getCause() + ")");
307
}
308
309
System.out.println("Doing queryNames to ensure connection alive");
310
names = mbsc.queryNames(null, null);
311
System.out.println("queryNames returned " + names);
312
313
System.out.println("Trying to throw unserializable exception");
314
try {
315
mbsc.invoke(on, "throwUnserializable", NO_OBJECTS, NO_STRINGS);
316
System.out.println("TEST FAILS: throw unserializable worked " +
317
"but should not");
318
ok = false;
319
} catch (IOException e) {
320
System.out.println("Success: got an IOException: " + e +
321
" (cause: " + e.getCause() + ")");
322
}
323
324
client.removeConnectionNotificationListener(cnListener);
325
ok = ok && !cnListener.failed;
326
327
client.close();
328
cs.stop();
329
330
if (ok)
331
System.out.println("Test passed for protocol " + proto);
332
333
System.out.println();
334
return ok;
335
}
336
337
private static class TestListener implements NotificationListener {
338
TestListener(LostListener ll) {
339
this.lostListener = ll;
340
}
341
342
public void handleNotification(Notification n, Object h) {
343
/* Connectors can handle unserializable notifications in
344
one of two ways. Either they can arrange for the
345
client to get a NotSerializableException from its
346
fetchNotifications call (RMI connector), or they can
347
replace the unserializable notification by a
348
JMXConnectionNotification.NOTIFS_LOST (JMXMP
349
connector). The former case is handled by code within
350
the connector client which will end up sending a
351
NOTIFS_LOST to our LostListener. The logic here
352
handles the latter case by converting it into the
353
former.
354
*/
355
if (n instanceof JMXConnectionNotification
356
&& n.getType().equals(JMXConnectionNotification.NOTIFS_LOST)) {
357
lostListener.handleNotification(n, h);
358
return;
359
}
360
361
synchronized (result) {
362
if (!n.getType().equals("interesting")
363
|| !n.getUserData().equals("known")) {
364
System.out.println("TestListener received strange notif: "
365
+ notificationString(n));
366
result.failed = true;
367
result.notifyAll();
368
} else {
369
result.knownCount++;
370
if (result.knownCount == NNOTIFS)
371
result.notifyAll();
372
}
373
}
374
}
375
376
private LostListener lostListener;
377
}
378
379
private static class LostListener implements NotificationListener {
380
public void handleNotification(Notification n, Object h) {
381
synchronized (result) {
382
handle(n, h);
383
}
384
}
385
386
private void handle(Notification n, Object h) {
387
if (!(n instanceof JMXConnectionNotification)) {
388
System.out.println("LostListener received strange notif: " +
389
notificationString(n));
390
result.failed = true;
391
result.notifyAll();
392
return;
393
}
394
395
JMXConnectionNotification jn = (JMXConnectionNotification) n;
396
if (!jn.getType().equals(jn.NOTIFS_LOST)) {
397
System.out.println("Ignoring JMXConnectionNotification: " +
398
notificationString(jn));
399
return;
400
}
401
final String msg = jn.getMessage();
402
if ((!msg.startsWith("Dropped ")
403
|| !msg.endsWith("classes were missing locally"))
404
&& (!msg.startsWith("Not serializable: "))) {
405
System.out.println("Surprising NOTIFS_LOST getMessage: " +
406
msg);
407
}
408
if (!(jn.getUserData() instanceof Long)) {
409
System.out.println("JMXConnectionNotification userData " +
410
"not a Long: " + jn.getUserData());
411
result.failed = true;
412
} else {
413
int lost = ((Long) jn.getUserData()).intValue();
414
result.lostCount += lost;
415
if (result.lostCount == NNOTIFS*2)
416
result.notifyAll();
417
}
418
}
419
}
420
421
private static class TestFilter implements NotificationFilter {
422
public boolean isNotificationEnabled(Notification n) {
423
return (n.getType().equals("interesting"));
424
}
425
}
426
427
private static class Result {
428
int knownCount, lostCount;
429
boolean failed;
430
}
431
private static Result result;
432
433
/* Send a bunch of notifications to exercise the logic to recover
434
from unknown notification classes. We have four kinds of
435
notifications: "known" ones are of a class known to the client
436
and which match its filters; "unknown" ones are of a class that
437
match the client's filters but that the client can't load;
438
"tricky" ones are unserializable; and "boring" notifications
439
are of a class that the client knows but that doesn't match its
440
filters. We emit NNOTIFS notifications of each kind. We do a
441
random shuffle on these 4*NNOTIFS notifications so it is likely
442
that we will cover the various different cases in the logic.
443
444
Specifically, what we are testing here is the logic that
445
recovers from a fetchNotifications request that gets a
446
ClassNotFoundException. Because the request can contain
447
several notifications, the client doesn't know which of them
448
generated the exception. So it redoes a request that asks for
449
just one notification. We need to be sure that this works when
450
that one notification is of an unknown class and when it is of
451
a known class, and in both cases when there are filtered
452
notifications that are skipped.
453
454
We are also testing the behaviour in the server when it tries
455
to include an unserializable notification in the response to a
456
fetchNotifications, and in the client when that happens.
457
458
If the test succeeds, the listener should receive the NNOTIFS
459
"known" notifications, and the connection listener should
460
receive an indication of NNOTIFS lost notifications
461
representing the "unknown" notifications.
462
463
We depend on some implementation-specific features here:
464
465
1. The buffer size is sufficient to contain the 4*NNOTIFS
466
notifications which are all sent at once, before the client
467
gets a chance to start receiving them.
468
469
2. When one or more notifications are dropped because they are
470
of unknown classes, the NOTIFS_LOST notification contains a
471
userData that is a Long with a count of the number dropped.
472
473
3. If a notification is not serializable on the server, the
474
client gets told about it somehow, rather than having it just
475
dropped on the floor. The somehow might be through an RMI
476
exception, or it might be by the server replacing the
477
unserializable notif by a JMXConnectionNotification.NOTIFS_LOST.
478
*/
479
private static boolean notifyTest(JMXConnector client,
480
MBeanServerConnection mbsc)
481
throws Exception {
482
System.out.println("Send notifications including unknown ones");
483
result = new Result();
484
LostListener ll = new LostListener();
485
client.addConnectionNotificationListener(ll, null, null);
486
TestListener nl = new TestListener(ll);
487
mbsc.addNotificationListener(on, nl, new TestFilter(), null);
488
mbsc.invoke(on, "sendNotifs", NO_OBJECTS, NO_STRINGS);
489
490
// wait for the listeners to receive all their notifs
491
// or to fail
492
long deadline = System.currentTimeMillis() + 60000;
493
long remain;
494
while ((remain = deadline - System.currentTimeMillis()) >= 0) {
495
synchronized (result) {
496
if (result.failed
497
|| (result.knownCount >= NNOTIFS
498
&& result.lostCount >= NNOTIFS*2))
499
break;
500
result.wait(remain);
501
}
502
}
503
Thread.sleep(2); // allow any spurious extra notifs to arrive
504
if (result.failed) {
505
System.out.println("TEST FAILS: Notification strangeness");
506
return false;
507
} else if (result.knownCount == NNOTIFS
508
&& result.lostCount == NNOTIFS*2) {
509
System.out.println("Success: received known notifications and " +
510
"got NOTIFS_LOST for unknown and " +
511
"unserializable ones");
512
return true;
513
} else if (result.knownCount >= NNOTIFS
514
|| result.lostCount >= NNOTIFS*2) {
515
System.out.println("TEST FAILS: Received too many notifs: " +
516
"known=" + result.knownCount + "; lost=" + result.lostCount);
517
return false;
518
} else {
519
System.out.println("TEST FAILS: Timed out without receiving " +
520
"all notifs: known=" + result.knownCount +
521
"; lost=" + result.lostCount);
522
return false;
523
}
524
}
525
526
public static interface TestMBean {
527
public Object getClientUnknown() throws Exception;
528
public void throwClientUnknown() throws Exception;
529
public void setServerUnknown(Object o) throws Exception;
530
public void useServerUnknown(Object o) throws Exception;
531
public Object getUnserializable() throws Exception;
532
public void setUnserializable(Object un) throws Exception;
533
public void throwUnserializable() throws Exception;
534
public void sendNotifs() throws Exception;
535
}
536
537
public static class Test extends NotificationBroadcasterSupport
538
implements TestMBean {
539
540
public Object getClientUnknown() {
541
return clientUnknown;
542
}
543
544
public void throwClientUnknown() throws Exception {
545
throw clientUnknown;
546
}
547
548
public void setServerUnknown(Object o) {
549
throw new IllegalArgumentException("setServerUnknown succeeded "+
550
"but should not have");
551
}
552
553
public void useServerUnknown(Object o) {
554
throw new IllegalArgumentException("useServerUnknown succeeded "+
555
"but should not have");
556
}
557
558
public Object getUnserializable() {
559
return unserializableObject;
560
}
561
562
public void setUnserializable(Object un) {
563
throw new IllegalArgumentException("setUnserializable succeeded " +
564
"but should not have");
565
}
566
567
public void throwUnserializable() throws Exception {
568
throw new Exception() {
569
private Object unserializable = unserializableObject;
570
};
571
}
572
573
public void sendNotifs() {
574
/* We actually send the same four notification objects
575
NNOTIFS times each. This doesn't particularly matter,
576
but note that the MBeanServer will replace "this" by
577
the sender's ObjectName the first time. Since that's
578
always the same, no problem. */
579
Notification known =
580
new Notification("interesting", this, 1L, 1L, "known");
581
known.setUserData("known");
582
Notification unknown =
583
new Notification("interesting", this, 1L, 1L, "unknown");
584
unknown.setUserData(clientUnknown);
585
Notification boring =
586
new Notification("boring", this, 1L, 1L, "boring");
587
Notification tricky =
588
new Notification("interesting", this, 1L, 1L, "tricky");
589
tricky.setUserData(unserializableObject);
590
591
// check that the tricky notif is indeed unserializable
592
try {
593
new ObjectOutputStream(new ByteArrayOutputStream())
594
.writeObject(tricky);
595
throw new RuntimeException("TEST INCORRECT: tricky notif is " +
596
"serializable");
597
} catch (NotSerializableException e) {
598
// OK: tricky notif is not serializable
599
} catch (IOException e) {
600
throw new RuntimeException("TEST INCORRECT: tricky notif " +
601
"serialization check failed");
602
}
603
604
/* Now shuffle an imaginary deck of cards where K, U, T, and
605
B (known, unknown, tricky, boring) each appear NNOTIFS times.
606
We explicitly seed the random number generator so we
607
can reproduce rare test failures if necessary. We only
608
use a StringBuffer so we can print the shuffled deck --
609
otherwise we could just emit the notifications as the
610
cards are placed. */
611
long seed = System.currentTimeMillis();
612
System.out.println("Random number seed is " + seed);
613
Random r = new Random(seed);
614
int knownCount = NNOTIFS; // remaining K cards
615
int unknownCount = NNOTIFS; // remaining U cards
616
int trickyCount = NNOTIFS; // remaining T cards
617
int boringCount = NNOTIFS; // remaining B cards
618
StringBuffer notifList = new StringBuffer();
619
for (int i = NNOTIFS * 4; i > 0; i--) {
620
int rand = r.nextInt(i);
621
// use rand to pick a card from the remaining ones
622
if ((rand -= knownCount) < 0) {
623
notifList.append('k');
624
knownCount--;
625
} else if ((rand -= unknownCount) < 0) {
626
notifList.append('u');
627
unknownCount--;
628
} else if ((rand -= trickyCount) < 0) {
629
notifList.append('t');
630
trickyCount--;
631
} else {
632
notifList.append('b');
633
boringCount--;
634
}
635
}
636
if (knownCount != 0 || unknownCount != 0
637
|| trickyCount != 0 || boringCount != 0) {
638
throw new RuntimeException("TEST INCORRECT: Shuffle failed: " +
639
"known=" + knownCount +" unknown=" +
640
unknownCount + " tricky=" + trickyCount +
641
" boring=" + boringCount +
642
" deal=" + notifList);
643
}
644
String notifs = notifList.toString();
645
System.out.println("Shuffle: " + notifs);
646
for (int i = 0; i < NNOTIFS * 4; i++) {
647
Notification n;
648
switch (notifs.charAt(i)) {
649
case 'k': n = known; break;
650
case 'u': n = unknown; break;
651
case 't': n = tricky; break;
652
case 'b': n = boring; break;
653
default:
654
throw new RuntimeException("TEST INCORRECT: Bad shuffle char: " +
655
notifs.charAt(i));
656
}
657
sendNotification(n);
658
}
659
}
660
}
661
662
private static String notificationString(Notification n) {
663
return n.getClass().getName() + "/" + n.getType() + " \"" +
664
n.getMessage() + "\" <" + n.getUserData() + ">";
665
}
666
667
//
668
private static class CNListener implements NotificationListener {
669
public void handleNotification(Notification n, Object o) {
670
if (n instanceof JMXConnectionNotification) {
671
JMXConnectionNotification jn = (JMXConnectionNotification)n;
672
if (JMXConnectionNotification.FAILED.equals(jn.getType())) {
673
failed = true;
674
}
675
}
676
}
677
678
public boolean failed = false;
679
}
680
}
681
682