author | Mark J. Nelson <Mark.J.Nelson@Sun.COM> |
Wed, 06 Aug 2008 16:29:39 -0600 | |
changeset 7298 | b69e27387f74 |
parent 0 | 68f95e015346 |
permissions | -rw-r--r-- |
0 | 1 |
/* |
2 |
* CDDL HEADER START |
|
3 |
* |
|
4 |
* The contents of this file are subject to the terms of the |
|
7298
b69e27387f74
6733918 Teamware has retired, please welcome your new manager, Mercurial
Mark J. Nelson <Mark.J.Nelson@Sun.COM>
parents:
0
diff
changeset
|
5 |
* Common Development and Distribution License (the "License"). |
b69e27387f74
6733918 Teamware has retired, please welcome your new manager, Mercurial
Mark J. Nelson <Mark.J.Nelson@Sun.COM>
parents:
0
diff
changeset
|
6 |
* You may not use this file except in compliance with the License. |
0 | 7 |
* |
8 |
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 |
* or http://www.opensolaris.org/os/licensing. |
|
10 |
* See the License for the specific language governing permissions |
|
11 |
* and limitations under the License. |
|
12 |
* |
|
13 |
* When distributing Covered Code, include this CDDL HEADER in each |
|
14 |
* file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 |
* If applicable, add the following below this CDDL HEADER, with the |
|
16 |
* fields enclosed by brackets "[]" replaced with your own identifying |
|
17 |
* information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 |
* |
|
19 |
* CDDL HEADER END |
|
20 |
*/ |
|
21 |
/* |
|
22 |
* Copyright (c) 1999 by Sun Microsystems, Inc. |
|
23 |
* All rights reserved. |
|
24 |
* |
|
25 |
*/ |
|
26 |
||
27 |
// ServerDATable.java: Abstract class for DA Table in the DA/SA server. |
|
28 |
// Author: James Kempf |
|
29 |
// Created On: Wed May 20 08:30:46 1998 |
|
30 |
// Last Modified By: James Kempf |
|
31 |
// Last Modified On: Tue Mar 9 12:36:37 1999 |
|
32 |
// Update Count: 124 |
|
33 |
// |
|
34 |
||
35 |
package com.sun.slp; |
|
36 |
||
37 |
import java.util.*; |
|
38 |
import java.net.*; |
|
39 |
||
40 |
/** |
|
41 |
* ServerDATable is an abstract class that provides the interface for DA |
|
42 |
* and scope discovery, storage of DA information from incoming DAAdverts, |
|
43 |
* and forwarding of registrations and deregistration to DAs having |
|
44 |
* the same scopes. A variety of implementations are possible. |
|
45 |
* The getServerDATable() method creates the right one from a subclass. |
|
46 |
* We keep separate track of the superclass DA table and the server |
|
47 |
* DA table so that these two classes can co-exist in the same JVM. |
|
48 |
* Note that the code for forwarding registrations must keep track of |
|
49 |
* only those registrations that were done on this host. It does this |
|
50 |
* by saving the registrations as they come in. The forwarding code |
|
51 |
* is optimized so that forwarding of a new message is fast, while |
|
52 |
* forwarding of a message due to discovery of a new DA is somewhat |
|
53 |
* slower. This helps assure that SA clients get good service. |
|
54 |
* |
|
55 |
* The ServerDATable also does active discovery for the SA server/DA, |
|
56 |
* in a separate thread. |
|
57 |
* |
|
58 |
* @author James Kempf |
|
59 |
*/ |
|
60 |
||
61 |
abstract class ServerDATable extends DATable { |
|
62 |
||
63 |
// The active discovery object. |
|
64 |
||
65 |
protected static ActiveDiscoverer activeDiscoverer = null; |
|
66 |
||
67 |
// The table of regs to forward. Keys are the reg URL and locale, values |
|
68 |
// are the SSrvReg objects. |
|
69 |
||
70 |
protected Hashtable forwardRegs = new Hashtable(); |
|
71 |
||
72 |
// This acts as a guard protecting an non-initialized DA table: |
|
73 |
// If the DA Table hasn't been populated by active discovery yet, |
|
74 |
// other threads accessing the DA table will block on readyLock. |
|
75 |
private static Object readyLock = new Object(); |
|
76 |
||
77 |
// Keeps track of which DAs support which SPIs. The actual mapping |
|
78 |
// is DA InetAddress to LinkedList of SPI Strings. recordNewDA |
|
79 |
// populates this. |
|
80 |
protected Hashtable daSPIsHash = new Hashtable(); |
|
81 |
||
82 |
/** |
|
83 |
* Get the right server DA table from the subclass. |
|
84 |
* |
|
85 |
* @return Table for handling DAs in the server. |
|
86 |
*/ |
|
87 |
||
88 |
static ServerDATable getServerDATable() |
|
89 |
throws ServiceLocationException { |
|
90 |
||
91 |
ServerDATable table = null; |
|
92 |
||
93 |
synchronized (readyLock) { |
|
94 |
||
95 |
// Note that we are expecting this subclass. We will get a |
|
96 |
// cast exception if somebody instantiated with a |
|
97 |
// DATable subclass. |
|
98 |
||
99 |
if (daTable != null) { |
|
100 |
return (ServerDATable)daTable; |
|
101 |
||
102 |
} |
|
103 |
||
104 |
conf = SLPConfig.getSLPConfig(); |
|
105 |
||
106 |
// Call the superclass method to link it. |
|
107 |
||
108 |
daTable = linkAndInstantiateFromProp(); |
|
109 |
||
110 |
table = (ServerDATable)daTable; |
|
111 |
||
112 |
// Advertise for *all* scopes. This is because we need to |
|
113 |
// be able to support clients that do user scoping. |
|
114 |
||
115 |
Vector useScopes = new Vector(); |
|
116 |
||
117 |
activeDiscoverer = |
|
118 |
new ActiveDiscoverer(Defaults.version, |
|
119 |
table, |
|
120 |
useScopes, |
|
121 |
conf.getMulticastAddress()); |
|
122 |
||
123 |
activeDiscoverer.start(); |
|
124 |
||
125 |
} // readyLock |
|
126 |
||
127 |
return table; |
|
128 |
||
129 |
} |
|
130 |
||
131 |
/** |
|
132 |
* Record a new DA. |
|
133 |
* |
|
134 |
* @param URL The DAAdvert URL. |
|
135 |
* @param scopes The scopes. |
|
136 |
* @param version DA version number. |
|
137 |
* @param attrs Attributes of DA. |
|
138 |
* @param spis SPIs supported by DA |
|
139 |
* @return True if recorded, false if not. |
|
140 |
*/ |
|
141 |
||
142 |
abstract long |
|
143 |
recordNewDA(ServiceURL url, |
|
144 |
Vector scopes, |
|
145 |
long timestamp, |
|
146 |
int version, |
|
147 |
Vector attrs, |
|
148 |
String spis); |
|
149 |
||
150 |
/** |
|
151 |
* Return a hashtable in ServiceTable.findServices() format (e.g. |
|
152 |
* URL's as keys, scopes as values) for DAs matching the query. |
|
153 |
* |
|
154 |
* @param query Query for DA attributes. |
|
155 |
*/ |
|
156 |
||
157 |
abstract Hashtable returnMatchingDAs(String query) |
|
158 |
throws ServiceLocationException; |
|
159 |
||
160 |
/** |
|
161 |
* Forward a registration or deregistration to all DAs that have matching |
|
162 |
* scopes. |
|
163 |
* |
|
164 |
* @param msg Registration or deregistration message, server side. |
|
165 |
* @param source The address of the source. |
|
166 |
*/ |
|
167 |
||
168 |
void forwardSAMessage(SrvLocMsg msg, InetAddress source) |
|
169 |
throws ServiceLocationException { |
|
170 |
||
171 |
SrvLocHeader hdr = msg.getHeader(); |
|
172 |
||
173 |
// If the message is not from this host (on any interface) |
|
174 |
// then don't forward it. |
|
175 |
||
176 |
if (!conf.isLocalHostSource(source)) { |
|
177 |
return; |
|
178 |
||
179 |
} |
|
180 |
||
181 |
// Record it so we can forward to a new DA. |
|
182 |
||
183 |
if (msg instanceof SSrvReg || msg instanceof CSrvReg) { |
|
184 |
ServiceURL url; |
|
185 |
if (msg instanceof SSrvReg) { |
|
186 |
url = ((SSrvReg)msg).URL; |
|
187 |
} else { |
|
188 |
url = ((CSrvReg)msg).URL; |
|
189 |
} |
|
190 |
||
191 |
String key = makeKey(url, hdr.locale); // need locale also... |
|
192 |
forwardRegs.put(key, msg); // fresh doesn't matter. |
|
193 |
||
194 |
} else { |
|
195 |
SSrvDereg smsg = (SSrvDereg)msg; |
|
196 |
||
197 |
// Only remove if tags are null. Otherwise, the updated record |
|
198 |
// will be sought. |
|
199 |
||
200 |
if (smsg.tags == null) { |
|
201 |
String key = makeKey(smsg.URL, hdr.locale); |
|
202 |
forwardRegs.remove(key); |
|
203 |
||
204 |
} |
|
205 |
} |
|
206 |
||
207 |
// We only forward registrations to v2 DAs because we are |
|
208 |
// acting as an SA server here. There is no requirement |
|
209 |
// for v2 SAs to communicate with v1 DAs. |
|
210 |
||
211 |
// Get a hashtable of DAs that match the scopes in the message. |
|
212 |
||
213 |
Hashtable daScopeRec = findDAScopes(hdr.scopes); |
|
214 |
||
215 |
// We are only concerned with the unicast key, since it contains |
|
216 |
// the DAs to which forwarding is required. |
|
217 |
||
218 |
Vector daRecs = (Vector)daScopeRec.get(UNICAST_KEY); |
|
219 |
||
220 |
// If there are no daRecs, then simply return. |
|
221 |
||
222 |
if (daRecs == null) { |
|
223 |
return; |
|
224 |
||
225 |
} |
|
226 |
||
227 |
// Otherwise, forward the registration to all DAs in the vector. |
|
228 |
||
229 |
int i, n = daRecs.size(); |
|
230 |
||
231 |
for (i = 0; i < n; i++) { |
|
232 |
DARecord rec = (DARecord)daRecs.elementAt(i); |
|
233 |
Vector daAddresses = rec.daAddresses; |
|
234 |
||
235 |
int j, m = daAddresses.size(); |
|
236 |
||
237 |
for (j = 0; j < m; j++) { |
|
238 |
InetAddress addr = (InetAddress)daAddresses.elementAt(j); |
|
239 |
||
240 |
// Don't forward if it's the host from which the registration |
|
241 |
// came. Otherwise, we're hosed. |
|
242 |
||
243 |
if (!source.equals(addr)) { |
|
244 |
forwardRegOrDereg(addr, msg); |
|
245 |
||
246 |
} |
|
247 |
} |
|
248 |
} |
|
249 |
} |
|
250 |
||
251 |
// Make a key for the service agent message table. |
|
252 |
||
253 |
private String makeKey(ServiceURL url, Locale locale) { |
|
254 |
||
255 |
return url.toString() + "/" + locale.toString(); |
|
256 |
||
257 |
} |
|
258 |
||
259 |
||
260 |
/** |
|
261 |
* Handle an incoming DAAdvert. Presence must be recorded in the |
|
262 |
* implementation specific server DA table and any registrations need |
|
263 |
* to be forwarded if the boot timestamp is different from the |
|
264 |
* old boot timestamp. |
|
265 |
* |
|
266 |
* @param advert Incoming DAAdvert. |
|
267 |
*/ |
|
268 |
||
269 |
void handleAdvertIn(CDAAdvert advert) { |
|
270 |
||
271 |
SrvLocHeader hdr = advert.getHeader(); |
|
272 |
||
273 |
// Remove if DA is going down. |
|
274 |
||
275 |
if (advert.isGoingDown()) { |
|
276 |
||
277 |
InetAddress addr = null; |
|
278 |
||
279 |
try { |
|
280 |
||
281 |
addr = InetAddress.getByName(advert.URL.getHost()); |
|
282 |
||
283 |
} catch (UnknownHostException ex) { |
|
284 |
conf.writeLog("unknown_da_address", |
|
285 |
new Object[] {advert.URL.getHost()}); |
|
286 |
||
287 |
return; |
|
288 |
} |
|
289 |
||
290 |
if (removeDA(addr, hdr.scopes)) { |
|
291 |
||
292 |
if (conf.traceDATraffic()) { |
|
293 |
conf.writeLog("sdat_delete_da", |
|
294 |
new Object[] { |
|
295 |
advert.URL, |
|
296 |
hdr.scopes}); |
|
297 |
} |
|
298 |
} |
|
299 |
||
300 |
} else { |
|
301 |
||
302 |
// verify the DAAdvert |
|
303 |
if (advert.authBlock != null) { |
|
304 |
try { |
|
305 |
AuthBlock.verifyAll(advert.authBlock); |
|
306 |
} catch (ServiceLocationException e) { |
|
307 |
if (conf.traceDrop()) { |
|
308 |
conf.writeLog("sdat_daadvert_vrfy_failed", |
|
309 |
new Object[] {advert.URL}); |
|
310 |
} |
|
311 |
return; |
|
312 |
} |
|
313 |
} |
|
314 |
||
315 |
long timestamp = |
|
316 |
recordNewDA(advert.URL, |
|
317 |
hdr.scopes, |
|
318 |
advert.timestamp, |
|
319 |
hdr.version, |
|
320 |
advert.attrs, |
|
321 |
advert.spis); |
|
322 |
||
323 |
// Don't forward if the advert was rejected, or if the |
|
324 |
// old timestamp greater than or equal to the new timestamp. |
|
325 |
// If the old timestamp is greater than or equal to the new, |
|
326 |
// it means that we have already forwarded to this DA. |
|
327 |
// IF the old timestamp is less, it means that |
|
328 |
// the DA has crashed and come up again since we last saw |
|
329 |
// it, so we may have missed forwarding something to it. |
|
330 |
||
331 |
if (timestamp >= advert.timestamp) { |
|
332 |
||
333 |
if (conf.traceDATraffic()) { |
|
334 |
conf.writeLog("sdat_add_da_no_forward", |
|
335 |
new Object[] { |
|
336 |
advert.URL, |
|
337 |
hdr.scopes, |
|
338 |
new Long(timestamp)}); |
|
339 |
} |
|
340 |
||
341 |
return; |
|
342 |
||
343 |
} |
|
344 |
||
345 |
if (conf.traceDATraffic()) { |
|
346 |
conf.writeLog("sdat_add_da", |
|
347 |
new Object[] { |
|
348 |
advert.URL, |
|
349 |
hdr.scopes, |
|
350 |
new Long(advert.timestamp)}); |
|
351 |
} |
|
352 |
||
353 |
// Forward existing registrations to the new advert. |
|
354 |
||
355 |
forwardRegistrations(advert.URL, hdr.scopes, |
|
356 |
advert.timestamp, hdr.version); |
|
357 |
} |
|
358 |
} |
|
359 |
||
360 |
// |
|
361 |
// Private methods. |
|
362 |
// |
|
363 |
||
364 |
private void |
|
365 |
forwardRegistrations(ServiceURL url, |
|
366 |
Vector scopes, |
|
367 |
long timestamp, |
|
368 |
int version) { |
|
369 |
||
370 |
// Wait a random amount of time before forwarding. |
|
371 |
||
372 |
try { |
|
373 |
||
374 |
Thread.currentThread().sleep(conf.getRandomWait()); |
|
375 |
||
376 |
} catch (InterruptedException ex) { |
|
377 |
||
378 |
} |
|
379 |
||
380 |
// Get the registrations to forward. |
|
381 |
||
382 |
Enumeration regs = forwardRegs.elements(); |
|
383 |
||
384 |
// Get the address of the DA. |
|
385 |
||
386 |
InetAddress addr = null; |
|
387 |
String host = url.getHost(); |
|
388 |
||
389 |
try { |
|
390 |
addr = InetAddress.getByName(host); |
|
391 |
||
392 |
} catch (UnknownHostException ex) { |
|
393 |
if (conf.traceDrop() || conf.traceDATraffic()) { |
|
394 |
conf.writeLog("sdat_drop_fwd", |
|
395 |
new Object[] { |
|
396 |
host}); |
|
397 |
||
398 |
} |
|
399 |
||
400 |
return; |
|
401 |
} |
|
402 |
||
403 |
ServiceTable serviceTable = null; |
|
404 |
||
405 |
try { |
|
406 |
||
407 |
serviceTable = ServiceTable.getServiceTable(); |
|
408 |
||
409 |
} catch (ServiceLocationException ex) { |
|
410 |
||
411 |
// By this time, we should have it. |
|
412 |
||
413 |
} |
|
414 |
||
415 |
// Forward the registrations. Keep track of any deleted elements. |
|
416 |
||
417 |
Vector deleted = new Vector(); |
|
418 |
||
419 |
while (regs.hasMoreElements()) { |
|
420 |
SrvLocMsg reg = (SrvLocMsg)regs.nextElement(); |
|
421 |
||
422 |
ServiceURL regurl; |
|
423 |
if (reg instanceof SSrvReg) { |
|
424 |
regurl = ((SSrvReg)reg).URL; |
|
425 |
} else { |
|
426 |
regurl = ((CSrvReg)reg).URL; |
|
427 |
} |
|
428 |
||
429 |
SrvLocHeader hdr = reg.getHeader(); |
|
430 |
||
431 |
// Get the record and modify the reg to reflect the |
|
432 |
// record. We must do this because the SA may have |
|
433 |
// changed the record since it was first registred |
|
434 |
// and we do not keep track of the changes here. |
|
435 |
||
436 |
ServiceStore.ServiceRecord rec = |
|
437 |
serviceTable.getServiceRecord(regurl, hdr.locale); |
|
438 |
||
439 |
// If the record is null, it means that the entry was |
|
440 |
// aged out. |
|
441 |
||
442 |
if (rec == null) { |
|
443 |
deleted.addElement(reg); |
|
444 |
||
445 |
} else { |
|
446 |
||
447 |
// Check that the scopes match. |
|
448 |
||
449 |
Vector sscopes = (Vector)hdr.scopes.clone(); |
|
450 |
||
451 |
DATable.filterScopes(sscopes, scopes, false); |
|
452 |
||
453 |
if (sscopes.size() <= 0) { |
|
454 |
continue; |
|
455 |
||
456 |
} |
|
457 |
||
458 |
if (reg instanceof SSrvReg) { |
|
459 |
SSrvReg sreg = (SSrvReg)reg; |
|
460 |
||
461 |
hdr.scopes = (Vector)hdr.scopes.clone(); |
|
462 |
sreg.attrList = (Vector)rec.getAttrList().clone(); |
|
463 |
sreg.URLSignature = rec.getURLSignature(); |
|
464 |
sreg.attrSignature = rec.getAttrSignature(); |
|
465 |
} |
|
466 |
||
467 |
forwardRegOrDereg(addr, reg); |
|
468 |
} |
|
469 |
||
470 |
} |
|
471 |
||
472 |
// Remove any deleted elements from the hashtable. |
|
473 |
// We do this in a separate loop because enumerations |
|
474 |
// aren't synchronized. |
|
475 |
||
476 |
int i, n = deleted.size(); |
|
477 |
||
478 |
for (i = 0; i < n; i++) { |
|
479 |
SrvLocMsg reg = (SrvLocMsg)deleted.elementAt(i); |
|
480 |
SrvLocHeader hdr = reg.getHeader(); |
|
481 |
ServiceURL regurl; |
|
482 |
if (reg instanceof SSrvReg) { |
|
483 |
regurl = ((SSrvReg)reg).URL; |
|
484 |
} else { |
|
485 |
regurl = ((CSrvReg)reg).URL; |
|
486 |
} |
|
487 |
||
488 |
String key = makeKey(regurl, hdr.locale); |
|
489 |
||
490 |
forwardRegs.remove(key); |
|
491 |
||
492 |
} |
|
493 |
||
494 |
} |
|
495 |
||
496 |
||
497 |
// Forward the registration or deregistration to the URL. |
|
498 |
||
499 |
private void forwardRegOrDereg(InetAddress addr, SrvLocMsg rqst) { |
|
500 |
SrvLocHeader hdr = rqst.getHeader(); |
|
501 |
||
502 |
// Don't forward to myself! Otherwise, nasty recursion happens. |
|
503 |
||
504 |
if (conf.isLocalHostSource(addr)) { |
|
505 |
return; |
|
506 |
||
507 |
} |
|
508 |
||
509 |
// If security is on, only forward if this DA can verify the authblocks |
|
510 |
if (conf.getHasSecurity()) { |
|
511 |
LinkedList spis = (LinkedList)daSPIsHash.get(addr); |
|
512 |
if (spis == null) { |
|
513 |
// internal error; skip this DA to be safe |
|
514 |
return; |
|
515 |
} |
|
516 |
||
517 |
Hashtable auths = null; |
|
518 |
if (rqst instanceof SSrvReg) { |
|
519 |
auths = ((SSrvReg)rqst).URLSignature; |
|
520 |
} else if (rqst instanceof SSrvDereg) { |
|
521 |
auths = ((SSrvDereg)rqst).URLSignature; |
|
522 |
} else { |
|
523 |
// shouldn't even be forwarding this! |
|
524 |
return; |
|
525 |
} |
|
526 |
||
527 |
// If each authblock is equiv to at least one SPI, forward the reg |
|
528 |
Enumeration abs = auths.elements(); |
|
529 |
while (abs.hasMoreElements()) { |
|
530 |
AuthBlock ab = (AuthBlock)abs.nextElement(); |
|
531 |
||
532 |
// check each DA SPI |
|
533 |
boolean daSPImatch = false; |
|
534 |
for (int SPIi = 0; SPIi < spis.size(); SPIi++) { |
|
535 |
if (AuthBlock.checkEquiv((String)spis.get(SPIi), ab)) { |
|
536 |
daSPImatch = true; |
|
537 |
break; |
|
538 |
} |
|
539 |
} |
|
540 |
||
541 |
if (!daSPImatch) { |
|
542 |
return; |
|
543 |
} |
|
544 |
} |
|
545 |
} |
|
546 |
||
547 |
if (conf.traceDATraffic()) { |
|
548 |
conf.writeLog("sdat_forward", |
|
549 |
new Object[] { |
|
550 |
Integer.toHexString(hdr.xid), |
|
551 |
addr}); |
|
552 |
||
553 |
} |
|
554 |
||
555 |
||
556 |
// Send it via TCP. DAs should understand TCP, and it's reliable. |
|
557 |
||
558 |
SrvLocMsg rply = null; |
|
559 |
||
560 |
try { |
|
561 |
||
562 |
// Construct the client side message, for outgoing. |
|
563 |
||
564 |
if (rqst instanceof SSrvReg) { |
|
565 |
SSrvReg rrqst = (SSrvReg)rqst; |
|
566 |
CSrvReg msg = new CSrvReg(hdr.fresh, |
|
567 |
hdr.locale, |
|
568 |
rrqst.URL, |
|
569 |
hdr.scopes, |
|
570 |
rrqst.attrList, |
|
571 |
rrqst.URLSignature, |
|
572 |
rrqst.attrSignature); |
|
573 |
rply = msg; |
|
574 |
||
575 |
} else if (rqst instanceof SSrvDereg) { |
|
576 |
SSrvDereg drqst = (SSrvDereg)rqst; |
|
577 |
CSrvDereg msg = new CSrvDereg(hdr.locale, |
|
578 |
drqst.URL, |
|
579 |
hdr.scopes, |
|
580 |
drqst.tags, |
|
581 |
drqst.URLSignature); |
|
582 |
rply = msg; |
|
583 |
||
584 |
} else if (rqst instanceof CSrvReg) { |
|
585 |
rply = rqst; |
|
586 |
||
587 |
} |
|
588 |
||
589 |
rply = Transact.transactTCPMsg(addr, rply, false); |
|
590 |
||
591 |
} catch (ServiceLocationException ex) { |
|
592 |
||
593 |
if (conf.traceDATraffic()) { |
|
594 |
conf.writeLog("sdat_forward_exception", |
|
595 |
new Object[] { |
|
596 |
Integer.toHexString(hdr.xid), |
|
597 |
addr, |
|
598 |
new Integer(ex.getErrorCode()), |
|
599 |
ex.getMessage()}); |
|
600 |
||
601 |
} |
|
602 |
} |
|
603 |
||
604 |
// Report any errors. |
|
605 |
||
606 |
if (rply == null || |
|
607 |
rply.getErrorCode() != ServiceLocationException.OK) { |
|
608 |
if (conf.traceDATraffic()) { |
|
609 |
conf.writeLog("sdat_forward_err", |
|
610 |
new Object[] { |
|
611 |
Integer.toHexString(hdr.xid), |
|
612 |
addr, |
|
613 |
(rply == null ? "<null>": |
|
614 |
Integer.toString(rply.getErrorCode()))}); |
|
615 |
||
616 |
} |
|
617 |
} |
|
618 |
} |
|
619 |
} |