1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package net.jini.lookup;
19
20 import java.io.IOException;
21 import java.rmi.RemoteException;
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.ConcurrentLinkedQueue;
28 import java.util.concurrent.CopyOnWriteArrayList;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Future;
31 import java.util.concurrent.LinkedBlockingQueue;
32 import java.util.concurrent.RunnableFuture;
33 import java.util.concurrent.ThreadPoolExecutor;
34 import java.util.concurrent.TimeUnit;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
37 import net.jini.config.Configuration;
38 import net.jini.config.ConfigurationException;
39 import net.jini.config.EmptyConfiguration;
40 import net.jini.config.NoSuchEntryException;
41 import net.jini.core.entry.CloneableEntry;
42 import net.jini.core.entry.Entry;
43 import net.jini.core.lease.Lease;
44 import net.jini.core.lease.UnknownLeaseException;
45 import net.jini.core.lookup.ServiceID;
46 import net.jini.core.lookup.ServiceItem;
47 import net.jini.core.lookup.ServiceRegistrar;
48 import net.jini.core.lookup.ServiceRegistration;
49 import net.jini.discovery.DiscoveryEvent;
50 import net.jini.discovery.DiscoveryListener;
51 import net.jini.discovery.DiscoveryManagement;
52 import net.jini.discovery.LookupDiscoveryManager;
53 import net.jini.lease.LeaseListener;
54 import net.jini.lease.LeaseRenewalEvent;
55 import net.jini.lease.LeaseRenewalManager;
56 import net.jini.security.BasicProxyPreparer;
57 import net.jini.security.ProxyPreparer;
58 import org.apache.river.constants.ThrowableConstants;
59 import org.apache.river.logging.LogUtil;
60 import org.apache.river.lookup.entry.LookupAttributes;
61 import org.apache.river.thread.DependencyLinker;
62 import org.apache.river.thread.ExtensibleExecutorService;
63 import org.apache.river.thread.ExtensibleExecutorService.RunnableFutureFactory;
64 import org.apache.river.thread.FutureObserver;
65 import org.apache.river.thread.NamedThreadFactory;
66 import org.apache.river.thread.wakeup.RetryTask;
67 import org.apache.river.thread.wakeup.WakeupManager;
68
69 /**
70 * A goal of any well-behaved service is to advertise the facilities and
71 * functions it provides by requesting residency within at least one lookup
72 * service. Making such a request of a lookup service is known as registering
73 * with, or <i>joining</i>, a lookup service. To demonstrate this good
74 * behavior, a service must comply with both the multicast discovery protocol
75 * and the unicast discovery protocol in order to discover the lookup services
76 * it is interested in joining. The service must also comply with the join
77 * protocol to register with the desired lookup services.
78 * <p>
79 * In order for a service to maintain its residency in the lookup services
80 * it has joined, the service must provide for the coordination, systematic
81 * renewal, and overall management of all leases on that residency. In
82 * addition to handling all discovery and join duties, as well as managing
83 * all leases on lookup service residency, the service must also provide
84 * for the coordination and management of any attribute sets with which
85 * it may have registered with the lookup services in which it resides.
86 * <p>
87 * This class performs all of the functions related to discovery, joining,
88 * service lease renewal, and attribute management which is required of a
89 * well-behaved service. Each of these activities is intimately involved
90 * with the maintenance of a service's residency in one or more lookup
91 * services (the service's join state), thus the name <code>JoinManager</code>.
92 * <p>
93 * This class should be employed by services, not clients. The use of this
94 * class in a wide variety of services can help minimize the work resulting
95 * from having to repeatedly implement this required functionality in each
96 * service. Note that this class is not remote. Services that wish to use
97 * this class will create an instance of this class in the service's address
98 * space to manage the entity's join state locally.
99 *
100 * <!-- Implementation Specifics -->
101 *
102 * The following implementation-specific items are discussed below:
103 * <ul><li> <a href="#jmConfigEntries">Configuring JoinManager</a>
104 * <li> <a href="#jmLogging">Logging</a>
105 * </ul>
106 *
107 * <a name="jmConfigEntries">
108 * <b><font size="+1">Configuring JoinManager</font></b>
109 * </a>
110 *
111 * This implementation of <code>JoinManager</code> supports the following
112 * configuration entries; where each configuration entry name is associated
113 * with the component name <code>net.jini.lookup.JoinManager</code>. Note
114 * that the configuration entries specified here are specific to this
115 * implementation of <code>JoinManager</code>. Unless otherwise stated, each
116 * entry is retrieved from the configuration only once per instance of
117 * this utility, where each such retrieval is performed in the constructor.
118 *
119 * <a name="discoveryManager"></a>
120 * <table summary="Describes the discoveryManager configuration entry"
121 * border="0" cellpadding="2">
122 * <tr valign="top">
123 * <th scope="col"> <font size="+1">•</font>
124 * <th scope="col" align="left" colspan="2"> <font size="+1">
125 * <code>discoveryManager</code></font>
126 *
127 * <tr valign="top"> <td> <th scope="row" align="right">
128 * Type: <td> {@link net.jini.discovery.DiscoveryManagement}
129 *
130 * <tr valign="top"> <td> <th scope="row" align="right">
131 * Default: <td> <code> new
132 * {@link net.jini.discovery.LookupDiscoveryManager#LookupDiscoveryManager(
133 * java.lang.String[],
134 * net.jini.core.discovery.LookupLocator[],
135 * net.jini.discovery.DiscoveryListener,
136 * net.jini.config.Configuration) LookupDiscoveryManager}(
137 * new java.lang.String[] {""},
138 * new {@link net.jini.core.discovery.LookupLocator}[0],
139 * null, config)</code>
140 *
141 * <tr valign="top"> <td> <th scope="row" align="right">
142 * Description:
143 * <td> The object used to manage the discovery processing
144 * performed by this utility. This entry will be retrieved
145 * from the configuration only if no discovery manager is
146 * specified in the constructor. Note that this object should
147 * not be shared with other components in the application that
148 * employs this utility.
149 * </table>
150 *
151 * <a name="leaseManager"></a>
152 * <table summary="Describes the leaseManager configuration entry"
153 * border="0" cellpadding="2">
154 * <tr valign="top">
155 * <th scope="col" > <font size="+1">•</font>
156 * <th scope="col" align="left" colspan="2"> <font size="+1">
157 * <code>leaseManager</code></font>
158 *
159 * <tr valign="top"> <td> <th scope="row" align="right">
160 * Type: <td> {@link net.jini.lease.LeaseRenewalManager}
161 *
162 * <tr valign="top"> <td> <th scope="row" align="right">
163 * Default: <td> <code> new
164 * {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
165 * net.jini.config.Configuration) LeaseRenewalManager}(config)</code>
166 *
167 * <tr valign="top"> <td> <th scope="row" align="right">
168 * Description:
169 * <td> The object used to manage each service lease returned
170 * to this utility when the service is registered with the
171 * the various discovered lookup services. This entry will
172 * be retrieved from the configuration only if no lease
173 * renewal manager is specified in the constructor.
174 * </table>
175 *
176 * <a name="maxLeaseDuration"></a>
177 * <table summary="Describes the maxLeaseDuration
178 * configuration entry" border="0" cellpadding="2">
179 * <tr valign="top">
180 * <th scope="col" > <font size="+1">•</font>
181 * <th scope="col" align="left" colspan="2"> <font size="+1">
182 * <code>maxLeaseDuration</code></font>
183 *
184 * <tr valign="top"> <td> <th scope="row" align="right">
185 * Type: <td> <code>long</code>
186 *
187 * <tr valign="top"> <td> <th scope="row" align="right">
188 * Default: <td> <code>Lease.FOREVER</code>
189 *
190 * <tr valign="top"> <td> <th scope="row" align="right">
191 * Description:
192 * <td> The maximum lease duration (in milliseconds) that is requested
193 * from each discovered lookup service on behalf of the service;
194 * both when the lease is initially requested, as well as when
195 * renewal of that lease is requested. Note that as this value is
196 * made smaller, renewal requests will be made more frequently
197 * while the service is up, and lease expiration will occur sooner
198 * when the service goes down.
199 * </table>
200 *
201 * <a name="registrarPreparer"></a>
202 * <table summary="Describes the registrarPreparer configuration entry"
203 * border="0" cellpadding="2">
204 * <tr valign="top">
205 * <th scope="col" > <font size="+1">•</font>
206 * <th scope="col" align="left" colspan="2"> <font size="+1">
207 * <code>registrarPreparer</code></font>
208 *
209 * <tr valign="top"> <td> <th scope="row" align="right">
210 * Type: <td> {@link net.jini.security.ProxyPreparer}
211 *
212 * <tr valign="top"> <td> <th scope="row" align="right">
213 * Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
214 * </code>
215 *
216 * <tr valign="top"> <td> <th scope="row" align="right">
217 * Description:
218 * <td> Preparer for the proxies to the lookup services that are
219 * discovered and used by this utility.
220 * <p>
221 * The following methods of the proxy returned by this preparer are
222 * invoked by this utility:
223 * <ul>
224 * <li>{@link net.jini.core.lookup.ServiceRegistrar#register register}
225 * </ul>
226 * </table>
227 *
228 * <a name="registrationPreparer"></a>
229 * <table summary="Describes the registrationPreparer configuration entry"
230 * border="0" cellpadding="2">
231 * <tr valign="top">
232 * <th scope="col" > <font size="+1">•</font>
233 * <th scope="col" align="left" colspan="2"> <font size="+1">
234 * <code>registrationPreparer</code></font>
235 *
236 * <tr valign="top"> <td> <th scope="row" align="right">
237 * Type: <td> {@link net.jini.security.ProxyPreparer}
238 *
239 * <tr valign="top"> <td> <th scope="row" align="right">
240 * Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
241 * </code>
242 *
243 * <tr valign="top"> <td> <th scope="row" align="right">
244 * Description:
245 * <td> Preparer for the proxies to the registrations returned to
246 * this utility upon registering the service with each discovered
247 * lookup service.
248 * <p>
249 * The following methods of the proxy returned by this preparer are
250 * invoked by this utility:
251 * <ul>
252 * <li>{@link net.jini.core.lookup.ServiceRegistration#getServiceID
253 * getServiceID}
254 * <li>{@link net.jini.core.lookup.ServiceRegistration#getLease
255 * getLease}
256 * <li>{@link net.jini.core.lookup.ServiceRegistration#addAttributes
257 * addAttributes}
258 * <li>{@link net.jini.core.lookup.ServiceRegistration#modifyAttributes
259 * modifyAttributes}
260 * <li>{@link net.jini.core.lookup.ServiceRegistration#setAttributes
261 * setAttributes}
262 * </ul>
263 * </table>
264 *
265 * <a name="serviceLeasePreparer"></a>
266 * <table summary="Describes the serviceLeasePreparer configuration entry"
267 * border="0" cellpadding="2">
268 * <tr valign="top">
269 * <th scope="col" > <font size="+1">•</font>
270 * <th scope="col" align="left" colspan="2"> <font size="+1">
271 * <code>serviceLeasePreparer</code></font>
272 *
273 * <tr valign="top"> <td> <th scope="row" align="right">
274 * Type: <td> {@link net.jini.security.ProxyPreparer}
275 *
276 * <tr valign="top"> <td> <th scope="row" align="right">
277 * Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
278 * </code>
279 *
280 * <tr valign="top"> <td> <th scope="row" align="right">
281 * Description:
282 * <td> Preparer for the leases returned to this utility through
283 * the registrations with each discovered lookup service with
284 * which this utility has registered the service.
285 * <p>
286 * Currently, none of the methods on the service lease returned
287 * by this preparer are invoked by this implementation of the utility.
288 * </table>
289 *
290 * <a name="executorService"></a>
291 * <table summary="Describes the executorService configuration entry"
292 * border="0" cellpadding="2">
293 * <tr valign="top">
294 * <th scope="col" > <font size="+1">•</font>
295 * <th scope="col" align="left" colspan="2"> <font size="+1">
296 * <code>executorService</code></font>
297 *
298 * <tr valign="top"> <td> <th scope="row" align="right">
299 * Type: <td> {@link java.util.concurrent.ExecutorService ExecutorService}
300 *
301 * <tr valign="top"> <td> <th scope="row" align="right">
302 * Default: <td> <code>new
303 * {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}(
304 * 15,
305 * 15,
306 * 15,
307 * TimeUnit.SECONDS,
308 * new {@link java.util.concurrent.LinkedBlockingQueue LinkedBlockingQueue}(),
309 * new {@link NamedThreadFactory NamedThreadFactory}("JoinManager executor thread", false))</code>
310 *
311 * <tr valign="top"> <td> <th scope="row" align="right">
312 * Description:
313 * <td> The object that pools and manages the various threads
314 * executed by this utility. This object
315 * should not be shared with other components in the
316 * application that employs this utility.
317 * </table>
318 *
319 * <a name="wakeupManager"></a>
320 * <table summary="Describes the wakeupManager configuration entry"
321 * border="0" cellpadding="2">
322 * <tr valign="top">
323 * <th scope="col" > <font size="+1">•</font>
324 * <th scope="col" align="left" colspan="2"> <font size="+1">
325 * <code>wakeupManager</code></font>
326 *
327 * <tr valign="top"> <td> <th scope="row" align="right">
328 * Type: <td> {@link org.apache.river.thread.wakeup.WakeupManager}
329 *
330 * <tr valign="top"> <td> <th scope="row" align="right">
331 * Default: <td> <code>new
332 * {@link org.apache.river.thread.wakeup.WakeupManager#WakeupManager(
333 * org.apache.river.thread.wakeup.WakeupManager.ThreadDesc)
334 * WakeupManager}(new
335 * {@link org.apache.river.thread.wakeup.WakeupManager.ThreadDesc}(null,true))</code>
336 *
337 * <tr valign="top"> <td> <th scope="row" align="right">
338 * Description:
339 * <td> Object that pools and manages the various tasks that are
340 * initially executed by the object corresponding to the
341 * <a href="#executorService"><code>executorService</code></a> entry
342 * of this component, but which fail during that initial execution.
343 * This object schedules the re-execution of such a failed task -
344 * in the <a href="#executorService"><code>executorService</code></a>
345 * object - at various times in the future, until either the
346 * task succeeds or the task has been executed the maximum
347 * number of allowable times, corresponding to the
348 * <a href="#wakeupRetries"><code>wakeupRetries</code></a>
349 * entry of this component. This object should not be shared
350 * with other components in the application that employs this
351 * utility.
352 * </table>
353 *
354 * <a name="wakeupRetries"></a>
355 * <table summary="Describes the wakeupRetries
356 * configuration entry" border="0" cellpadding="2">
357 * <tr valign="top">
358 * <th scope="col" > <font size="+1">•</font>
359 * <th scope="col" align="left" colspan="2"> <font size="+1">
360 * <code>wakeupRetries</code></font>
361 *
362 * <tr valign="top"> <td> <th scope="row" align="right">
363 * Type: <td> <code>int</code>
364 *
365 * <tr valign="top"> <td> <th scope="row" align="right">
366 * Default: <td> <code>6</code>
367 *
368 * <tr valign="top"> <td> <th scope="row" align="right">
369 * Description:
370 * <td> The maximum number of times a failed task is allowed to
371 * be executed by the object corresponding to the
372 * <a href="#wakeupManager"><code>wakeupManager</code></a>
373 * entry of this component.
374 * </table>
375 *
376 * <a name="jmLogging">
377 *
378 * <b><font size="+1">Logging</font></b>
379 *
380 * </a>
381 *
382 * This implementation of <code>JoinManager</code> uses the
383 * {@link Logger} named <code>net.jini.lookup.JoinManager</code>
384 * to log information at the following logging levels: <p>
385 *
386 * <table border="1" cellpadding="5"
387 * summary="Describes the information logged by JoinManager,
388 * and the levels at which that information is logged">
389 *
390 * <caption>
391 * <b><code>net.jini.lookup.JoinManager</code></b>
392 * </caption>
393 *
394 * <tr> <th scope="col"> Level</th>
395 * <th scope="col"> Description</th>
396 * </tr>
397 *
398 * <tr>
399 * <td>{@link java.util.logging.Level#INFO INFO}</td>
400 * <td>when a task is stopped because of a definite exception</td>
401 * </tr>
402 * <tr>
403 * <td>{@link java.util.logging.Level#INFO INFO}</td>
404 * <td>
405 * when a task is stopped because it has exceeded the maximum number of
406 * times the task is allowed to be tried/re-tried
407 * </td>
408 * </tr>
409 * <tr>
410 * <td>{@link java.util.logging.Level#INFO INFO}</td>
411 * <td>when any exception occurs while attempting to prepare a proxy</td>
412 * </tr>
413 * <tr>
414 * <td>{@link java.util.logging.Level#FINER FINER}</td>
415 * <td>
416 * when any exception (other than the more serious exceptions logged
417 * at higher levels) occurs in a task
418 * </td>
419 * </tr>
420 * <tr>
421 * <td>{@link java.util.logging.Level#FINEST FINEST}</td>
422 * <td>
423 * when an <code>IllegalStateException</code> occurs upon attempting to
424 * discard a lookup service
425 * </td>
426 * </tr>
427 * <tr>
428 * <td>{@link java.util.logging.Level#FINEST FINEST}</td>
429 * <td>whenever any task is started</td>
430 * </tr>
431 *
432 * <tr>
433 * <td>{@link java.util.logging.Level#FINEST FINEST}</td>
434 * <td>whenever any task completes successfully</td>
435 * </tr>
436 *
437 * <tr>
438 * <td>{@link java.util.logging.Level#FINEST FINEST}</td>
439 * <td>whenever a proxy is prepared</td>
440 * </tr>
441 * </table>
442 * <p>
443 *
444 * @author Sun Microsystems, Inc.
445 *
446 * @see net.jini.discovery.DiscoveryManagement
447 * @see net.jini.lease.LeaseRenewalManager
448 * @see java.util.logging.Level
449 * @see java.util.logging.Logger
450 */
451 public class JoinManager {
452
453 /** Implementation Note:
454 *
455 * This class executes a number of tasks asynchronously. Each task is
456 * initially queued in a <code>org.apache.river.thread.TaskManager</code>
457 * instance, which executes each task in a separate thread. In this
458 * way, an upper bound is placed on the number of threads executing
459 * concurrently at any one time; that is, the number of concurrent
460 * threads is "throttled".
461 *
462 * In addition to throttling the number of concurrent threads, the
463 * use of a task manager, in conjunction with the task configuration,
464 * provides a level of resiliency with respect to down/unresponsive
465 * lookup services.
466 *
467 * Recall from the specification that the primary function of a join
468 * manager is to maintain and update the state (registrations,
469 * attribute augmentations/replacements/changes, etc.) of the join
470 * manager's single associated service with all of the desired lookup
471 * services. Because updating a service's state in a lookup service
472 * involves remote communication, such update operations are performed
473 * asynchronously, in separate tasks. If those operations are not
474 * performed in separate tasks, then a communication problem with
475 * one of the lookup services while performing one operation could
476 * prevent (or significantly slow) the execution of all future
477 * state update operations; causing all processing in the join manager
478 * to degrade or even hang indefinitely.
479 *
480 * Although performing each update operation in a separate task thread
481 * prevents a down/unresponsive lookup service from blocking other
482 * processing in the join manager, it does not guarantee consistency
483 * of the service's state in each "up" lookup service, with the state
484 * expected by the client that requested the state changes.
485 *
486 * For example, suppose the client first requests that the service's
487 * attributes be replaced with a new set, and then immediately after
488 * the attribute replacement request, the client requests that the
489 * service's attributes be augmented by yet another set of attributes.
490 * Unless control is imposed on the order of execution of the tasks
491 * that perform the attribute replacement and augmentation, there is
492 * a possibility that the service's state in one or more of the lookup
493 * service's will have experienced the attribute augmentation prior to
494 * the attribute replacement, rather than the replacement followed by
495 * the augmentation, as the client would have expected. Thus, not only
496 * can the service's state in one or more of the lookup services be
497 * inconsistent with what the client expects, but it can also be
498 * inconsistent with one or more of the other lookup services with
499 * which the service is registered.
500 *
501 * To prevent the possibility of inconsistencies such as those just
502 * described, the state update operations are grouped according to
503 * the particular lookup service with which they are associated. Each
504 * such grouping is implemented as a task, executed by the task
505 * manager employed by this implementation of <code>JoinManager</code>,
506 * containing a queue of sub-tasks (the actual state update operations)
507 * that are executed by the main task in the same order in which they
508 * were requested by the client.
509 *
510 * Each main task executed by this join manager's task manager is a
511 * sub-class of the abstract class <code>RetryTask</code>, defined in
512 * the package <code>org.apache.river.thread</code>, which implements
513 * the <code>org.apache.river.thread.TaskManager.Task</code> interface.
514 * The association of each such task with a particular lookup service,
515 * and with a unique sequence number is reflected in the fields of this
516 * class. The unique sequence number associated with each main task
517 * is exploited by the <code>runAfter</code> method defined in
518 * <code>RetryTask</code> to guarantee that the service managed by
519 * this <code>JoinManager</code> is assigned only one service ID.
520 *
521 * Because of the relationship between the main tasks and
522 * <code>RetryTask</code>, those concrete main tasks created in this
523 * implementation of <code>JoinManager</code> can be processed by either
524 * a task manager or a wakeup manager; and this fact is exploited to
525 * provide a failure recovery mechanism that can tolerate task "storms".
526 * Task storms can occur in systems that contain numerous services,
527 * where each service employs its own join manager to handle its join
528 * state with the desired lookup services. A "registration request storm"
529 * is an example of one type of task storm.
530 *
531 * A registration request storm can occur when a system is initially
532 * started, or when it recovers from a system-wide failure, when each
533 * service's join manager attempts to register with each discovered
534 * lookup service; resulting in a flood - or "storm" - of registration
535 * requests at each lookup service. Because the accept queue under some
536 * operating systems is - by default - very small, it is possible that,
537 * in the face of such a large number of concurrent requests, one or
538 * more of the lookup services will not be able to process the requests
539 * as fast as they are arriving in the queue. When this happens, the
540 * associated task in the join manager typically encounters a failure
541 * such as a <code>java.rmi.ConnectException</code>.
542 *
543 * To deal with situations such as that described above, this
544 * <code>JoinManager</code> implementation employs a wakeup manager
545 * in addition to a task manager. When a main task is created, it is
546 * initially executed by a task manager. If a failure is encountered,
547 * and if the nature of that failure indicates that retrying the task
548 * will <i>definitely</i> fail again, then the associated lookup service
549 * is discarded so that the task can be retried when the lookup service
550 * is rediscovered. But if it is not clear that retrying the task will
551 * again fail, (that is, the failure is <i>indefinite</i>), then the
552 * task is queued for re-execution at a later time in a wakeup manager.
553 * This process is repeated - employing a "backoff" strategy - until one
554 * of the following events occurs:
555 *
556 * <p><ul>
557 * <li> the task succeeds
558 * <li> a definite failure is encountered
559 * <li> the task has been executed the maximum number times allowed
560 * </ul><p>
561 *
562 * The maximum number of times a task is allowed to be retried before
563 * giving up and discarding the associated lookup service can be changed
564 * from its default value by setting the <code>wakeupRetries</code>
565 * configuration entry for this component.
566 *
567 * @see org.apache.river.thread.TaskManager
568 * @see org.apache.river.thread.wakeup.WakeupManager
569 * @see org.apache.river.thread.TaskManager.Task
570 * @see org.apache.river.thread.RetryTask
571 * @see org.apache.river.constants.ThrowableConstants
572 */
573
574 /** Abstract base class from which all of the task classes are derived. */
575 private class ProxyRegTask extends RetryTask implements Comparable<ProxyRegTask> {
576 private final long[] sleepTime = { 5*1000, 10*1000, 15*1000,
577 20*1000, 25*1000, 30*1000 };
578 // volatile fields only mutated while synchronized on proxyReg.taskList
579 private volatile int tryIndx = 0;
580 private volatile int nRetries = 0;
581 private final ProxyReg proxyReg;
582 private final int seqN;
583
584 /** Basic constructor; simply stores the input parameters */
585 ProxyRegTask(ProxyReg proxyReg, int seqN) {
586 super(JoinManager.this.executor,JoinManager.this.wakeupMgr);
587 this.proxyReg = proxyReg;
588 this.seqN = seqN;
589 }//end constructor
590
591 /** Executes the current instance of this task once, queuing it
592 * for retry at a later time and returning <code>false</code>
593 * upon failure. This method attempts to execute all of the tasks
594 * associated with the lookup service referenced in this task's
595 * <code>proxyReg</code> field. Order of execution is important,
596 * and this method executes the tasks in the <code>proxyReg</code>'s
597 * <code>taskList</code> in a FIFO order.
598 *
599 * Note that tasks may be added to the <code>taskList</code> of
600 * the <code>proxyReg</code> during the execution of this method.
601 *
602 * Upon successfully executing all of the tasks in the
603 * <code>taskList</code>, this method returns <code>true</code>
604 * and the current instance of this task is not executed again.
605 * For each unsuccessful execution of a task in the
606 * <code>taskList</code>, this method returns <code>false</code>,
607 * which causes the task to be scheduled by the
608 * <code>WakeupManager</code> to be executed again at a later
609 * time, as indicated by the value returned by <code>retryTime</code>.
610 */
611 @Override
612 public boolean tryOnce() {
613 while(true) {
614 JoinTask t;
615 synchronized(proxyReg.taskList) {
616 if(proxyReg.taskList.isEmpty()) {
617 proxyReg.proxyRegTask = null;
618 return true;
619 }//endif
620 t = proxyReg.taskList.get(0);
621 }//end sync
622 try {
623 t.run();
624 synchronized(proxyReg.taskList) {
625 if( !proxyReg.taskList.isEmpty() ) {
626 JoinTask task = proxyReg.taskList.get(0);
627 if (task == t) proxyReg.taskList.remove(0);
628 }//endif
629 /* reset the retry info for the next task in the list */
630 tryIndx = 0;
631 nRetries = 0;
632 }//end sync
633
634 } catch (Exception e) {
635 return stopTrying(e);
636 }
637 }//end loop
638 }//end tryOnce
639
640 /** Returns the next absolute time (in milliseconds) at which another
641 * execution of this task should be made (after the previous
642 * attempt has failed).
643 */
644 @Override
645 public long retryTime() {
646 long nextTryTime = System.currentTimeMillis();
647 synchronized (proxyReg.taskList){
648 nextTryTime += sleepTime[tryIndx];
649 if(tryIndx < sleepTime.length-1) tryIndx++;//don't go past end
650 nRetries++;
651 }
652 return nextTryTime;
653 }//end retryTime
654
655 /** Returns true if the current instance of this task must be run
656 * after any task already in the task manager queue.
657 *
658 * It is important that when the join manager is constructed with
659 * a <code>null</code> service ID (where it is desired that
660 * a unique service ID be generated on the service's behalf),
661 * that only the first task in the task manager's queue be run; no
662 * other tasks in the queue should be run while that first task
663 * is running. This is because the first sub-task executed by
664 * the first main task in the task manager's queue will always be
665 * a <code>RegisterTask</code>. And during the execution of that
666 * first sub-task (if the service ID has not yet been set), the
667 * service ID generated by the associated lookup service is retrieved
668 * and stored for use in all future lookup service registration
669 * tasks, Once the service ID is set by that first registration
670 * sub-task, all future main tasks (and their associated registration
671 * sub-tasks) can be run in parallel; each using the same service ID.
672 * If this is not done, then the registration sub-tasks would be
673 * run in parallel, each assigning a different ID to the service.
674 *
675 * This method guarantees that until the service's ID is set,
676 * only one registration sub-task is run; that is, one task
677 * doesn't start until the currently running task has completed,
678 * and a non-<code>null</code> service ID is assigned to the service.
679 *
680 * Executing the main tasks sequentially until the service ID is
681 * retrieved and stored must also be guaranteed because the currently
682 * running registration task may fail to register the service
683 * (because of a <code>RemoteException</code>), and thus may fail
684 * to obtain an ID for the service. This method guarantees then
685 * that each main task (and thus, each registration sub-task) will
686 * run in sequence until one of those tasks completes successfully;
687 * and from that point on, this method guarantees that all other
688 * queued tasks will run in parallel.
689 *
690 * @param tasks the tasks with which to compare the current task
691 * @param size elements with index less than size are considered
692 */
693 public boolean dependsOn(ProxyRegTask t) {
694 return seqN > t.getSeqN();
695 }
696
697 /* If the service's ID has already been set, then it's okay
698 * to run all ProxyRegTask's in parallel, otherwise, the
699 * ProxyRegTask with the lowest sequence number should be run.
700 */
701 public boolean hasDeps(){
702 return serviceItem.serviceID == null;
703 }
704
705 /** Accessor method that returns the instance of <code>ProxyReg</code>
706 * (the lookup service) associated with the task represented by
707 * the current instance of this class.
708 */
709 public ProxyReg getProxyReg() {
710 return proxyReg;
711 }//end getProxy
712
713 /** Accessor method that returns the unique sequence number associated
714 * with the task represented by the current instance of this class.
715 */
716 public int getSeqN() {
717 return seqN;
718 }//end getSeqN
719
720 /** Convenience method called by the child tasks when they encounter
721 * an exception. If the given exception indicates that retrying the
722 * task would definitely fail, or if the maximum allowable number
723 * of retries of the task has been exceeded, then this method will
724 * do the following:
725 * - remove all pending tasks that are to be run after this task
726 * - cancel this task
727 * - discard the look service associated with this task
728 * - return <code>true</code> (which stops the wakeup manager
729 * from retrying this task
730 * otherwise, this method returns <code>false</code>, which indicates
731 * that the wakeup manager should not stop trying to successfully
732 * execute the task.
733 */
734 protected boolean stopTrying(Exception e) {
735 int exCat = ThrowableConstants.retryable(e);
736 if( (exCat != ThrowableConstants.INDEFINITE)
737 || (nRetries >= maxNRetries) )
738 {
739 removeTasks(proxyReg);//cancel and clear all related tasks
740 proxyReg.fail(e);
741 return true;//don't try again
742 }//endif
743 logger.log(Level.FINER,
744 "JoinManager - failure, will retry later", e);
745 return false;//try this task again later
746 }//end stopTrying
747
748 @Override
749 public int compareTo(ProxyRegTask o) {
750 if (seqN < o.seqN) return -1;
751 if (seqN > o.seqN) return 1;
752 return 0;
753 }
754
755 @Override
756 public boolean equals(Object o){
757 if (!(o instanceof ProxyRegTask)) return false;
758 ProxyRegTask that = (ProxyRegTask) o;
759 if (this.seqN != that.seqN) return false;
760 return this.proxyReg.equals(that.proxyReg);
761 }
762
763 @Override
764 public int hashCode() {
765 int hash = 5;
766 hash = 79 * hash + (this.proxyReg != null ? this.proxyReg.hashCode() : 0);
767 hash = 79 * hash + this.seqN;
768 return hash;
769 }
770 }//end class ProxyRegTask
771
772 private static final class ProxyRegTaskQueue implements FutureObserver {
773 // CacheTasks pending completion.
774 private final ConcurrentLinkedQueue<ProxyRegTask> pending;
775 private final ExecutorService executor;
776
777 private ProxyRegTaskQueue(ExecutorService e){
778 this.pending = new ConcurrentLinkedQueue<ProxyRegTask>();
779 executor = e;
780 }
781
782 private Future submit(ProxyRegTask t){
783 pending.offer(t);
784 t.addObserver(this);
785 if (t.hasDeps()) {
786 List<ObservableFuture> deps = new LinkedList<ObservableFuture>();
787 Iterator<ProxyRegTask> it = pending.iterator();
788 while (it.hasNext()){
789 ProxyRegTask c = it.next();
790 if (t.dependsOn(c)) {
791 deps.add(c);
792 }
793 }
794 if (deps.isEmpty()){
795 executor.submit(t);
796 } else {
797 DependencyLinker linker = new DependencyLinker(executor, deps, t);
798 linker.register();
799 }
800 } else {
801 executor.submit(t);
802 }
803 return t;
804 }
805
806 @Override
807 public void futureCompleted(Future e) {
808 pending.remove(e);
809 }
810 }
811
812 /** Abstract base class from which all the sub-task classes are derived. */
813 private static abstract class JoinTask {
814
815 /** Data structure referencing the task's associated lookup service */
816 protected final ProxyReg proxyReg;
817
818 /** Basic constructor; simply stores the input parameters */
819 JoinTask(ProxyReg proxyReg) {
820 this.proxyReg = proxyReg;
821 }//end constructor
822
823 /** Method executed in a separate thread created by the task manager */
824 public abstract void run() throws Exception;
825
826 }//end class JoinTask
827
828 /** Task that asynchronously registers the service associated with this
829 * join manager with the lookup service referenced by the current
830 * instance of this class.
831 */
832 private static class RegisterTask extends JoinTask {
833 /** Attributes with which to register the service. These attributes
834 * must not change during the registration process performed in
835 * this this task.
836 */
837 final Entry[] regAttrs;
838
839 /** Constructor that associates this task with the lookup service
840 * referenced in the given <code>ProxyReg</code> parameter.
841 *
842 * @param proxyReg data structure that references the lookup service
843 * with which the service is to be registered
844 * @param regAttrs array of Entry objects whose elements are the
845 * attributes with which to register the service.
846 * The caller of this constructor should take steps
847 * to guarantee that the contents of this parameter
848 * do not change during the registration process
849 * performed in this task.
850 */
851 RegisterTask(ProxyReg proxyReg, Entry[] regAttrs) {
852 super(proxyReg);
853 this.regAttrs = regAttrs;
854 }//end constructor
855
856 /** Attempts to register this join manager's service with the lookup
857 * service referenced in this task's proxyReg field.
858 */
859 @Override
860 public void run() throws Exception {
861 logger.finest("JoinManager - RegisterTask started");
862 proxyReg.register(regAttrs);
863 logger.finest("JoinManager - RegisterTask completed");
864 }//end run
865
866 }//end class RegisterTask
867
868 /** Task that asynchronously re-registers the service associated with this
869 * join manager with the lookup service referenced by the current
870 * instance of this class.
871 *
872 * This task is typically executed when the service's lease with the
873 * referenced lookup service has expired.
874 */
875 private class LeaseExpireNotifyTask extends JoinTask {
876 /** Attributes with which to re-register the service. These attributes
877 * must not change during the registration process performed in
878 * this this task.
879 */
880 Entry[] regAttrs;
881 /** Constructor that associates this task with the lookup service
882 * referenced in the given <code>ProxyReg</code> parameter.
883 *
884 * @param proxyReg data structure that references the lookup service
885 * with which the service is to be re-registered
886 * @param regAttrs array of Entry objects whose elements are the
887 * attributes with which to re-register the service.
888 * The caller of this constructor should take steps
889 * to guarantee that the contents of this parameter
890 * do not change during the registration process
891 * performed in this task.
892 */
893 LeaseExpireNotifyTask(ProxyReg proxyReg, Entry[] regAttrs) {
894 super(proxyReg);
895 this.regAttrs = regAttrs;
896 }//end constructor
897
898 /** Attempts to re-register this join manager's service with the
899 * lookup service referenced by the current instance of this class.
900 */
901 @Override
902 public void run() throws Exception {
903 logger.finest("JoinManager - LeaseExpireNotifyTask started");
904 if(joinSet.contains(proxyReg)) proxyReg.register(regAttrs);
905 logger.finest("JoinManager - LeaseExpireNotifyTask completed");
906 }//end run
907
908 }//end class LeaseExpireNotifyTask
909
910 /** Task that asynchronously requests the cancellation of the lease
911 * on the <code>ServiceRegistration</code> referenced by the given
912 * <code>ProxyReg</code> object. The lease to be cancelled was granted
913 * by the lookup service whose proxy is also referenced by the given
914 * <code>ProxyReg</code> object. This task is executed whenever that
915 * lookup service is discarded.
916 *
917 * Note that although this task is executed upon receipt of any type
918 * of lookup service discard event, there is one type of discard
919 * that this task is actually intended to address: the so-called
920 * "lost-interest" discard. A discard corresponding to a loss of
921 * interest is an indication that the discarded lookup service is
922 * still up and available, but the discovery manager employed by
923 * this join manager (not the join manager or the service itself)
924 * discards the lookup service because the service is no longer
925 * interested in being registered with that lookup service. This
926 * loss of interest is caused by a change in either the lookup
927 * service's member groups or the service's groups-to-discover.
928 * Such a change in groups would typically be made administratively,
929 * through either the lookup service's administrative interface or
930 * through the service's administrative interface (or both).
931 *
932 * When the lookup service is discarded because of a loss of
933 * interest, there is a time period in which the service is still
934 * registered with the lookup service, even though the service no
935 * longer wishes to be registered with that lookup service. To
936 * address this, the lease is cancelled so that the service is
937 * removed from the lookup service in a much more timely fashion
938 * than simply allowing the lease to expire.
939 *
940 * As stated above, this task is also executed upon receipt of
941 * the other types of discard event. But because those other
942 * discard types occur when the lookup service is no longer
943 * available, the lease cancellation that is attempted here has
944 * no significant effect.
945 *
946 * @see DiscMgrListener#discarded
947 */
948 private class DiscardProxyTask extends JoinTask {
949 /** Constructor that provides this task with access (through the given
950 * <code>ProxyReg</code> parameter) to the lease to be cancelled.
951 *
952 * @param proxyReg data structure that references the
953 * <code>ServiceRegistration</code> and associated
954 * lease whose cancellation is to be requested
955 */
956 DiscardProxyTask(ProxyReg proxyReg) {
957 super(proxyReg);
958 }//end constructor
959
960 /** Requests the cancellation of the lease on the
961 * <code>ServiceRegistration</code> that is referenced in the
962 * <code>proxyReg</code> data structure.
963 */
964 @Override
965 public void run() {
966 logger.finest("JoinManager --> DiscardProxyTask started");
967 Lease svcLease = proxyReg != null ? proxyReg.serviceLease : null;
968 if( svcLease != null ) {
969 try {
970 svcLease.cancel();
971 } catch (Exception e) { /*ignore*/ }
972 }//endif
973 logger.finest("JoinManager - DiscardProxyTask completed");
974 }//end run
975
976 }//end class DiscardProxyTask
977
978 /** Task that asynchronously augments the attributes associated with this
979 * join manager's service in the lookup service referenced by the
980 * current instance of this class.
981 */
982 private static class AddAttributesTask extends JoinTask {
983 /** The new attribute values with which the service's current
984 * attributes will be augmented, replaced, or changed.
985 */
986 protected final Entry[] attrSets;
987
988 /** Constructor that associates this task with the lookup service
989 * referenced in the given <code>ProxyReg</code> parameter.
990 *
991 * @param proxyReg data structure that references the lookup service
992 * in which the service's attributes should be
993 * augmented
994 * @param newAttrs the attributes with which to augment the
995 * service's current set of attributes
996 */
997 AddAttributesTask(ProxyReg proxyReg, Entry[] newAttrs) {
998 super(proxyReg);
999 this.attrSets = (Entry[])newAttrs.clone();
1000 }//end constructor
1001
1002 /** Performs the actual attribute augmentation, replacement, or
1003 * modification work. This method is typically overridden by
1004 * sub-classes of this class.
1005 */
1006 protected void doAttributes(ProxyReg proxyReg) throws Exception {
1007 logger.finest("JoinManager - AddAttributesTask started");
1008 proxyReg.addAttributes(attrSets);
1009 logger.finest("JoinManager - AddAttributesTask completed");
1010 }//end AddAttributesTask.doAttributes
1011
1012 /** Attempts to either augment, replace, or modify the attributes
1013 * of this join manager's service in the lookup service referenced
1014 * by the current instance of this class. Which action is taken --
1015 * augmentation, replacement, or modification -- is dependent on the
1016 * definition of the <code>doAttributes/code> method.
1017 */
1018 @Override
1019 public void run() throws Exception {
1020 doAttributes(proxyReg);
1021 }//end run
1022
1023 }//end class AddAttributesTask
1024
1025 /** Task that asynchronously replaces the attributes associated with this
1026 * join manager's service in the lookup service referenced by the
1027 * current instance of this class.
1028 */
1029 private static final class SetAttributesTask extends AddAttributesTask {
1030 /** Constructor that associates this task with the lookup service
1031 * referenced in the given <code>ProxyReg</code> parameter.
1032 *
1033 * @param proxyReg data structure that references the lookup
1034 * service in which the service's attributes
1035 * should be replaced
1036 * @param replacementAttrs the attributes with which to replace the
1037 * service's current set of attributes
1038 */
1039 SetAttributesTask(ProxyReg proxyReg, Entry[] replacementAttrs){
1040 super(proxyReg, replacementAttrs);
1041 }//end constructor
1042
1043 /** Performs the actual attribute replacement work. */
1044 @Override
1045 protected void doAttributes(ProxyReg proxyReg) throws Exception {
1046 logger.finest("JoinManager - SetAttributesTask started");
1047 proxyReg.setAttributes(attrSets);
1048 logger.finest("JoinManager - SetAttributesTask completed");
1049 }//end SetAttributesTask.doAttributes
1050
1051 }//end class SetAttributesTask
1052
1053 /** Task that asynchronously modifies the attributes associated with this
1054 * join manager's service in the lookup service referenced by the
1055 * current instance of this class.
1056 */
1057 private static final class ModifyAttributesTask extends AddAttributesTask {
1058 private final Entry[] attrSetTemplates;
1059 /** Constructor that associates this task with the lookup service
1060 * referenced in the given <code>ProxyReg</code> parameter.
1061 *
1062 * @param proxyReg data structure that references the lookup
1063 * service in which the service's attributes
1064 * should be changed
1065 * @param attrSetTemplates the attribute templates that are used to
1066 * select (through attribute matching) the
1067 * attributes to change in the service's
1068 * current set of attributes
1069 * @param attrChanges the attributes containing the changes to
1070 * make to the attributes in the service's
1071 * current set that are selected through
1072 * attribute matching with the
1073 * attrSetTemplates parameter
1074 */
1075 ModifyAttributesTask( ProxyReg proxyReg,
1076 Entry[] attrSetTemplates,
1077 Entry[] attrChanges )
1078 {
1079 super(proxyReg, attrChanges);
1080 this.attrSetTemplates = (Entry[])attrSetTemplates.clone();
1081 }//end constructor
1082
1083 /** Performs the actual attribute modification work. */
1084 @Override
1085 protected void doAttributes(ProxyReg proxyReg) throws Exception {
1086 logger.finest("JoinManager - ModifyAttributesTask started");
1087 proxyReg.modifyAttributes(attrSetTemplates, attrSets);
1088 logger.finest("JoinManager - ModifyAttributesTask completed");
1089 }//end ModifyAttributesTask.doAttributes
1090
1091 }//end class ModifyAttributesTask
1092
1093 /** Wrapper class in which each instance corresponds to a lookup
1094 * service to discover, and with which this join manager's service
1095 * should be registered.
1096 */
1097 private class ProxyReg implements FutureObserver{
1098
1099 /** Class that is registered as a listener with this join manager's
1100 * lease renewal manager. That lease renewal manager manages the
1101 * lease granted to this join manager's associated service by the
1102 * lookup service referenced in the proxy object associated with
1103 * this class (<code>ProxyReg</code>).
1104 *
1105 * If the lease expires in the lookup service before the lease
1106 * renewal manager requests renewal of the lease, then upon sending
1107 * that renewal request, the lease renewal manager will receive an
1108 * <code>UnknownLeaseException</code> from the lookup service.
1109 * As a result, the lease renewal manager removes the expired lease
1110 * so that no further renewal attempts are made for that lease,
1111 * and then sends to this listener a <code>LeaseRenewalEvent</code>,
1112 * containing an <code>UnknownLeaseException</code>.
1113 *
1114 * Alternatively, suppose that at the time the lease renewal manager
1115 * is about to request the renewal of the lease, the lease renewal
1116 * manager determines that the expiration time of the lease has
1117 * actually passed. In this case, since there is no reason to
1118 * send the renewal request, the lease renewal manager instead
1119 * removes the expired lease (again, so that no further renewal
1120 * attempts are made for that lease), and then sends a
1121 * <code>LeaseRenewalEvent</code> to this listener to indicate
1122 * that the lease has expired. The difference between this case,
1123 * and the case described previously is that in this case the
1124 * <code>LeaseRenewalEvent</code> contains no exception (that is,
1125 * <code>LeaseRenewalEvent.getException</code> returns
1126 * <code>null</code>).
1127 *
1128 * Both situations described above indicate that the lease
1129 * referenced by the event received by this listener has expired.
1130 * Thus, the normal course of action should be to attempt to
1131 * re-register the service with the lookup service that originally
1132 * granted the lease that has expired.
1133 *
1134 * Prior to re-registering the service, the joinSet is examined to
1135 * determine if it contains a ProxyReg object that is equivalent to
1136 * the ProxyReg object referencing the current instance of this
1137 * listener class. That is, using <code>equals</code>, it is
1138 * determined whether the joinSet contains either ProxyReg.this,
1139 * or a new instance of ProxyReg that is equal to ProxyReg.this.
1140 * If the joinSet does not contain such a ProxyReg, then the lookup
1141 * service must have been discarded and not yet re-discovered; in
1142 * which case, there is no need to attempt to re-register with that
1143 * lookup service, since it is unavailable.
1144 *
1145 * If it is determined that the joinSet does contain either
1146 * ProxyReg.this or a new ProxyReg equivalent to ProxyReg.this,
1147 * then re-registration should be attempted, but only if the lease
1148 * associated with the ProxyReg in the joinSet is equal to the
1149 * expired lease referenced in the event received by this listener.
1150 * Equality of those leases is an indication that the lease on the
1151 * service's current registration has expired; thus, an attempt to
1152 * re-register the service should be made.
1153 *
1154 * If the lease associated with the ProxyReg from the joinSet
1155 * does not equal the expired lease from the event, then
1156 * re-registration should not be attempted. This is because
1157 * the inequality of the leases is an indication that the lease
1158 * renewal event received by this listener was the result of an
1159 * <code>UnknownLeaseException</code> that occurs when the
1160 * ProxyReg in the joinSet is a new ProxyReg, different from
1161 * ProxyReg.this, and the lease renewal manager requests the
1162 * renewal of the (now invalid) lease associated with that old
1163 * ProxyReg.this; not the valid lease associated with the new
1164 * ProxyReg.
1165 *
1166 * A scenario such as that just described can occur when the
1167 * lookup service is discarded, rediscovered, and the service is
1168 * re-registered, resulting in a new ProxyReg (with new lease)
1169 * being placed in the joinSet, replacing the previous ProxyReg
1170 * (ProxyReg.this). But before the old, expired lease is removed
1171 * from the lease renewal manager, an attempt to renew the old
1172 * lease is made. Such an attempt can occur because the lease
1173 * renewal manager may be in the process of requesting the renewal
1174 * of that lease (or may have queued such a request) just prior to,
1175 * or at the same time as, when the lease removal request is made
1176 * during discard processing. When the request is made to renew
1177 * the expired lease, an <code>UnknownLeaseException</code> occurs
1178 * and a lease renewal event is sent to this listener.
1179 *
1180 * If, upon receiving an event such as that just described, the
1181 * service were to be re-registered, the current valid service
1182 * registration would be replaced, a new lease would be granted,
1183 * and the corresponding ProxyReg currently contained in the joinSet
1184 * would be replaced with a new ProxyReg. Additionally, the now
1185 * invalid lease corresponding to the ProxyReg that was just
1186 * replaced would remain in the lease renewal manager. This means
1187 * that an attempt to renew that lease will eventually be made and
1188 * will fail, and the cycle just described will repeat indefinitely.
1189 *
1190 * Thus, for the reasons stated above, re-registration is attempted
1191 * only if the lease associated with the ProxyReg contained in the
1192 * joinSet is equal to the expired lease referenced in the lease
1193 * renewal event received by this listener.
1194 */
1195 private class DiscLeaseListener implements LeaseListener {
1196 @Override
1197 public void notify(LeaseRenewalEvent e) {
1198 Throwable ex = e.getException();
1199 if ( (ex == null) || (ex instanceof UnknownLeaseException) ) {
1200 removeTasks(ProxyReg.this);
1201 Lease expiredLease = e.getLease();
1202 // Maybe re-register
1203 Iterator<ProxyReg> it = joinSet.iterator();
1204 while (it.hasNext()){
1205 ProxyReg next = it.next();
1206 if (!ProxyReg.this.equals(next)) continue;
1207 if(expiredLease.equals(next.serviceLease)) {
1208 // Okay to re-register
1209 addTask(
1210 new LeaseExpireNotifyTask (ProxyReg.this,
1211 (Entry[])lookupAttr.clone()));
1212 }//endif
1213 }
1214 } else {
1215 fail(ex);
1216 }//endif
1217 }//end notify
1218 }//end class DiscLeaseListener
1219
1220 /** The <code>ProxyRegTask</code> that instantiated this
1221 * <code>ProxyReg</code>.
1222 */
1223 volatile ProxyRegTask proxyRegTask;// writes sync on taskList
1224 /** The <i>prepared</i> proxy to the lookup service referenced by
1225 * this class, and with which this join manager's service will be
1226 * registered.
1227 */
1228 final ServiceRegistrar proxy;
1229 /** The <i>prepared</i> registration proxy returned by this class'
1230 * associated lookup service when this join manager registers its
1231 * associated service.
1232 *
1233 * Writes to reference synchronized on JoinManager.this, but not referent
1234 * as it has foreign remote methods.
1235 */
1236 volatile ServiceRegistration srvcRegistration = null;
1237 /* The <i>prepared</i> proxy to the lease on the registration of this
1238 * join manager's service with the this class' associated lookup
1239 * service.
1240 */
1241 volatile Lease serviceLease = null;
1242 /** The set of sub-tasks that are to be executed in order for the
1243 * lookup service associated with the current instance of this class.
1244 */
1245 final List<JoinTask> taskList = new ArrayList<JoinTask>();
1246 /** The instance of <code>DiscLeaseListener</code> that is registered
1247 * with the lease renewal manager that handles the lease of this join
1248 * manger's service.
1249 */
1250 final List<Future> runningTasks = new ArrayList<Future>();
1251
1252 private final DiscLeaseListener dListener = new DiscLeaseListener();
1253
1254 /** Constructor that associates this class with the lookup service
1255 * referenced in the given <code>ProxyReg</code> parameter.
1256 *
1257 * @param proxy data structure that references the lookup service on
1258 * which the sub-tasks referenced in this class will be
1259 * executed in order
1260 */
1261 public ProxyReg(ServiceRegistrar proxy) {
1262 if(proxy == null) throw new IllegalArgumentException
1263 ("proxy can't be null");
1264 this.proxy = proxy;
1265 }//end constructor
1266
1267 @Override
1268 public void futureCompleted(Future e) {
1269 synchronized (runningTasks){
1270 runningTasks.remove(e);
1271 }
1272 }
1273
1274 public void terminate(){
1275 synchronized (runningTasks){
1276 Iterator<Future> it = runningTasks.iterator();
1277 while (it.hasNext()){
1278 it.next().cancel(false);
1279 }
1280 runningTasks.clear();
1281 }
1282 }
1283
1284 /** Convenience method that adds new sub-tasks to this class'
1285 * task queue.
1286 *
1287 * @param task the task to add to the task queue
1288 */
1289 public void addTask(JoinTask task) {
1290 if(bTerminated) return;
1291 Future future = null;
1292 synchronized(taskList) {
1293 taskList.add(task);
1294 if(this.proxyRegTask == null) {
1295 this.proxyRegTask = new ProxyRegTask(this,taskSeqN++);
1296 this.proxyRegTask.addObserver(this);
1297 future = proxyRegTaskQueue.submit(this.proxyRegTask);
1298 }//endif
1299 }//end sync(taskList)
1300 synchronized (runningTasks){
1301 runningTasks.add(future);
1302 }
1303 }//end addTask
1304
1305 /** Registers the service associated with this join manager with the
1306 * the lookup service corresponding to this class. Additionally,
1307 * this method retrieves the lease granted by the lookup service
1308 * on the service's registration, and passes that lease to the
1309 * lease renewal manager. If a <code>ServiceIDListener</code>
1310 * has been registered with this join manager, this method will
1311 * send to that listener a notification containing the service's ID.
1312 */
1313 public void register(Entry[] srvcAttrs) throws Exception {
1314 if(proxy == null) throw new RuntimeException("proxy is null");
1315 /* The lookup service proxy was already prepared at discovery */
1316 ServiceItem tmpSrvcItem;
1317 ServiceItem item;
1318 srvcRegistration = null;
1319 //accessing serviceItem.serviceID
1320 item = serviceItem;
1321 tmpSrvcItem = new ServiceItem(item.serviceID,
1322 item.service,
1323 srvcAttrs);
1324 /* Retrieve and prepare the proxy to the service registration */
1325 ServiceRegistration tmpSrvcRegistration
1326 = proxy.register(tmpSrvcItem, renewalDuration);
1327 try {
1328 tmpSrvcRegistration =
1329 (ServiceRegistration)registrationPreparer.prepareProxy
1330 ( tmpSrvcRegistration );
1331 logger.finest
1332 ("JoinManager - ServiceRegistration proxy prepared");
1333 } catch(Exception e) {
1334 LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
1335 "register", "JoinManager - failure during " +
1336 "preparation of ServiceRegistration proxy: {0}",
1337 new Object[] { tmpSrvcRegistration }, e);
1338 throw e; //rethrow the exception since proxy may be unusable
1339 }
1340 /* Retrieve and prepare the proxy to the service lease */
1341 Lease svcLease = tmpSrvcRegistration.getLease();
1342 try {
1343 this.serviceLease =
1344 (Lease)serviceLeasePreparer.prepareProxy(svcLease);
1345 logger.finest("JoinManager - service lease proxy prepared");
1346 } catch(Exception e) {
1347 LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
1348 "register", "JoinManager - failure during " +
1349 "preparation of service lease proxy: {0}",
1350 new Object[] { svcLease }, e);
1351 throw e; //rethrow the exception since proxy may be unusable
1352 }
1353 leaseRenewalMgr.renewUntil(svcLease, Lease.FOREVER,
1354 renewalDuration, dListener);
1355 ServiceID tmpID = null;
1356 srvcRegistration = tmpSrvcRegistration;
1357 ServiceID id = srvcRegistration.getServiceID();
1358 synchronized (JoinManager.this){
1359 item = serviceItem;
1360 if(item.serviceID == null) {
1361 serviceItem = new ServiceItem(id, item.service, item.attributeSets);
1362 tmpID = id;
1363 }//endif
1364 }
1365 if( (tmpID != null) && (callback != null) ) {
1366 callback.serviceIDNotify(tmpID);
1367 }//endif
1368 }//end ProxyReg.register
1369
1370 /** With respect to the lookup service referenced in this class
1371 * and with which this join manager's service is registered, this
1372 * method associates with that service a new set of attributes -- in
1373 * addition to that service's current set of attributes.
1374 */
1375 public void addAttributes(Entry[] attSet) throws Exception {
1376 ServiceRegistration sr = srvcRegistration;
1377 if (sr != null) sr.addAttributes(attSet);
1378 }//end ProxyReg.addAttributes
1379
1380 /** With respect to the lookup service referenced in this class
1381 * and with which this join manager's service is registered, this
1382 * method changes that service's current attributes by selecting
1383 * the attributes to change using the given first parameter;
1384 * and identifying the desired changes to make through the
1385 * contents of the second parameter.
1386 */
1387 public void modifyAttributes(Entry[] templ, Entry[] attSet)
1388 throws Exception
1389 {
1390 ServiceRegistration sr = srvcRegistration;
1391 if (sr != null) sr.modifyAttributes(templ, attSet);
1392 }//end ProxyReg.modifyAttributes
1393
1394 /** With respect to the lookup service referenced in this class
1395 * and with which this join manager's service is registered, this
1396 * method replaces that service's current attributes with a new
1397 * set of attributes.
1398 */
1399 public void setAttributes(Entry[] attSet) throws Exception {
1400 ServiceRegistration sr = srvcRegistration;
1401 if (sr != null) sr.setAttributes(attSet);
1402 }//end ProxyReg.setAttributes
1403
1404 /** Convenience method that encapsulates appropriate behavior when
1405 * failure is encountered related to the current instance of this
1406 * class. This method discards the lookup service proxy associated
1407 * with this object, and logs the stack trace of the given
1408 * <code>Throwable</code> according to the logging levels specified
1409 * for this utility.
1410 *
1411 * Note that if the discovery manager employed by this join manager
1412 * has been terminated, then the attempt to discard the lookup
1413 * service proxy will result in an <code>IllegalStateException</code>.
1414 * Since this method is called only within the tasks run by
1415 * this join manager, and since propagating an
1416 * <code>IllegalStateException</code> out into the
1417 * <code>ThreadGroup</code> of those tasks is undesirable, this
1418 * method will not propagate the <code>IllegalStateException</code>
1419 * that occurs as a result of an attempt to discard a lookup
1420 * service proxy from the discovery manager employed by this
1421 * join manager.
1422 *
1423 * For more information, refer to Bug 4490355.
1424 */
1425 public void fail(Throwable e) {
1426 if(bTerminated) return;
1427 LogUtil.logThrow(logger, Level.INFO, ProxyReg.class, "fail",
1428 "JoinManager - failure for lookup service proxy: {0}",
1429 new Object[] { proxy }, e);
1430 try {
1431 discMgr.discard(proxy);
1432 } catch(IllegalStateException e1) {
1433 logger.log(Level.FINEST,
1434 "JoinManager - cannot discard lookup, "
1435 +"discovery manager already terminated",
1436 e1);
1437 }
1438 }//end ProxyReg.fail
1439
1440 /** Returns true if the both objects' associated proxies are equal. */
1441 @Override
1442 public boolean equals(Object obj) {
1443 if (obj instanceof ProxyReg) {
1444 return proxy.equals( ((ProxyReg)obj).proxy );
1445 } else {
1446 return false;
1447 }//endif
1448 }//end ProxyReg.equals
1449
1450 /** Returns the hash code of the proxy referenced in this class. */
1451 @Override
1452 public int hashCode() {
1453 return proxy.hashCode();
1454 }//end hashCode
1455
1456 }//end class ProxyReg
1457
1458 /* Listener class for discovery/discard notification of lookup services. */
1459 private class DiscMgrListener implements DiscoveryListener {
1460 /* Invoked when new or previously discarded lookup is discovered. */
1461 @Override
1462 public void discovered(DiscoveryEvent e) {
1463 ServiceRegistrar[] proxys
1464 = (ServiceRegistrar[])e.getRegistrars();
1465 int l = proxys.length;
1466 for(int i=0;i<l;i++) {
1467 /* Prepare the proxy to the discovered lookup service
1468 * before interacting with it.
1469 */
1470 try {
1471 proxys[i]
1472 = (ServiceRegistrar)registrarPreparer.prepareProxy
1473 (proxys[i]);
1474 logger.log(Level.FINEST, "JoinManager - discovered "
1475 +"lookup service proxy prepared: {0}",
1476 proxys[i]);
1477 } catch(Exception e1) {
1478 LogUtil.logThrow(logger, Level.INFO,
1479 DiscMgrListener.class, "discovered", "failure "
1480 + "preparing discovered ServiceRegistrar proxy: "
1481 + "{0}", new Object[] { proxys[i] }, e1);
1482 discMgr.discard(proxys[i]);
1483 continue;
1484 }
1485 /* If the serviceItem is a lookup service, don't need to
1486 * register it with itself since a well-defined lookup
1487 * service will always register with itself.
1488 */
1489 if( !proxys[i].equals(serviceItem.service) ) {
1490 ProxyReg proxyReg = new ProxyReg(proxys[i]);
1491 if( !joinSet.contains(proxyReg) ) {
1492 joinSet.add(proxyReg);
1493 proxyReg.addTask(new RegisterTask
1494 (proxyReg,
1495 (Entry[])lookupAttr.clone()));
1496 }//endif
1497 }//endif
1498 }//end loop
1499 }//end discovered
1500
1501 /* Invoked when previously discovered lookup is discarded. */
1502 @Override
1503 public void discarded(DiscoveryEvent e) {
1504 ServiceRegistrar[] proxys
1505 = (ServiceRegistrar[])e.getRegistrars();
1506 int l = proxys.length;
1507 for(int i=0;i<l;i++) {
1508 ProxyReg proxyReg = findReg(proxys[i]);
1509 if(proxyReg != null) {
1510 removeTasks(proxyReg);
1511 joinSet.remove(proxyReg);
1512 try {
1513 leaseRenewalMgr.remove( proxyReg.serviceLease );
1514 } catch(UnknownLeaseException ex) { /*ignore*/ }
1515 proxyReg.addTask(new DiscardProxyTask(proxyReg));
1516 }//endif
1517 }//end loop
1518 }//end discarded
1519 }//end class DiscMgrListener
1520
1521 /* Name of this component; used in config entry retrieval and the logger.*/
1522 private static final String COMPONENT_NAME = "net.jini.lookup.JoinManager";
1523 /* Logger used by this utility. */
1524 private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
1525 /** Maximum number of concurrent tasks that can be run in any default
1526 * ExecutorService created by this class.
1527 */
1528 private static final int MAX_N_TASKS = 15;
1529 /** Whenever a task is created in this join manager, it is assigned a
1530 * unique sequence number so that the task is not run prior to the
1531 * execution, and completion of, any other tasks with which that task
1532 * is associated (tasks are grouped by the <code>ProxyReg</code> with
1533 * which each task is associated). This field contains the value of
1534 * the sequence number assigned to the most recently created task.
1535 */
1536 private int taskSeqN = 0; // access sync on taskList
1537 /** Task manager for the various tasks executed by this join manager.
1538 * On the first attempt to execute any task is managed by this
1539 * <code>ExecutorService</code> so that the number of concurrent threads
1540 * can be bounded. If one or more of those attempts fails, a
1541 * <code>WakeupManager</code> is used (through the use of a
1542 * <code>RetryTask</code>) to schedule - at a later time (employing a
1543 * "backoff strategy") - the re-execution of each failed task in this
1544 * <code>ExecutorService</code>.
1545 */
1546 private final ExecutorService executor;
1547 private final ProxyRegTaskQueue proxyRegTaskQueue;
1548 /** Maximum number of times a failed task is allowed to be re-executed. */
1549 private final int maxNRetries;
1550 /** Wakeup manager for the various tasks executed by this join manager.
1551 * After an initial failure of any task executed by this join manager,
1552 * the failed task is managed by this <code>WakeupManager</code>; which
1553 * schedules the re-execution of the failed task - in the task manager -
1554 * at various times in the future until either the task succeeds or the
1555 * task has been executed the maximum number of allowable times. This
1556 * wakeup manager is supplied to the <code>RetryTask</code>) that
1557 * performs the actual task execution so that when termination of this
1558 * join manager is requested, all tasks scheduled for retry by this
1559 * wakeup manager can be cancelled.
1560 */
1561 private final WakeupManager wakeupMgr;
1562 /** Contains the reference to the service that is to be registered with
1563 * all of the desired lookup services referenced by <code>discMgr</code>.
1564 */
1565 private volatile ServiceItem serviceItem = null; // writes sync on JoinManager.this
1566 /** Contains the attributes with which to associate the service in each
1567 * of the lookup services with which this join manager registers the
1568 * service.
1569 */
1570 private volatile Entry[] lookupAttr = null; // writes sync on JoinManager.this
1571 /** Contains the listener -- instantiated by the entity that constructs
1572 * this join manager -- that will receive an event containing the
1573 * service ID assigned to this join manager's service by one of the
1574 * lookup services with which that service is registered.
1575 */
1576 private final ServiceIDListener callback;
1577 /** Contains elements of type <code>ProxyReg</code> where each element
1578 * references a proxy to one of the lookup services with which this
1579 * join manager's service is registered.
1580 */
1581 private final List<ProxyReg> joinSet = new CopyOnWriteArrayList<ProxyReg>();
1582 /** Contains the discovery manager that discovers the lookup services
1583 * with which this join manager will register its associated service.
1584 */
1585 private final DiscoveryManagement discMgr;
1586 /** Contains the discovery listener registered by this join manager with
1587 * the discovery manager so that this join manager is notified whenever
1588 * one of the desired lookup services is discovered or discarded.
1589 */
1590 private final DiscMgrListener discMgrListener ;
1591 /** Flag that indicate whether the discovery manager employed by this
1592 * join manager was created by this join manager itself, or by the
1593 * entity that constructed this join manager.
1594 */
1595 private final boolean bCreateDiscMgr;
1596 /** Contains the lease renewal manager that renews all of the leases
1597 * this join manager's service holds with each lookup service with which
1598 * it has been registered.
1599 */
1600 private final LeaseRenewalManager leaseRenewalMgr;
1601 /** The value to use as the <code>renewDuration</code> parameter
1602 * when invoking the lease renewal manager's <code>renewUntil</code>
1603 * method to add a service lease to manage. This value represents,
1604 * effectively, the time interval (in milliseconds) over which each
1605 * managed lease must be renewed. As this value is made smaller,
1606 * renewal requests will be made more frequently while the service
1607 * is up, and lease expirations will occur sooner when the service
1608 * goes down.
1609 */
1610 private final long renewalDuration;
1611 /** Flag that indicates if this join manager has been terminated. */
1612 private volatile boolean bTerminated = false; // write access sync on this.
1613 /* Preparer for the proxies to the lookup services that are discovered
1614 * and used by this utility.
1615 */
1616 private final ProxyPreparer registrarPreparer;
1617 /* Preparer for the proxies to the registrations returned to this utility
1618 * upon registering the service with each discovered lookup service.
1619 */
1620 private final ProxyPreparer registrationPreparer;
1621 /* Preparer for the proxies to the leases returned to this utility through
1622 * the registrations with each discovered lookup service with which this
1623 * utility has registered the service.
1624 */
1625 private final ProxyPreparer serviceLeasePreparer;
1626
1627 /**
1628 * Constructs an instance of this class that will register the given
1629 * service reference with all discovered lookup services and, through
1630 * an event sent to the given <code>ServiceIDListener</code> object,
1631 * communicate the service ID assigned by the first lookup service
1632 * with which the service is registered. This constructor is typically
1633 * used by services which have not yet been assigned a service ID.
1634 * <p>
1635 * The value input to the <code>serviceProxy</code> parameter represents
1636 * the service reference (proxy) to register with each discovered lookup
1637 * service. If the <code>Object</code> input to that parameter is not
1638 * <code>Serializable</code>, an <code>IllegalArgumentException</code>
1639 * is thrown. If <code>null</code> is input to that parameter, a
1640 * <code>NullPointerException</code> is thrown.
1641 * <p>
1642 * The value input to the <code>attrSets</code> parameter is an array
1643 * of <code>Entry</code> objects, none of whose elements may be
1644 * <code>null</code>, that represents the new set of attributes to
1645 * associate with the new service reference to be registered. Passing
1646 * <code>null</code> as the value of the <code>attrSets</code> parameter
1647 * is equivalent to passing an empty array. If any of the elements
1648 * of the <code>attrSets</code> array are <code>null</code>, a
1649 * <code>NullPointerException</code> is thrown. The set of attributes
1650 * passed in this parameter will be associated with the service in all
1651 * future join processing until those attributes are changed through
1652 * an invocation of a method on this class such as,
1653 * <code>addAttributes</code>, <code>setAttributes</code>,
1654 * <code>modifyAttributes</code>, or <code>replaceRegistration</code>.
1655 * <p>
1656 * When constructing this utility, the service supplies an object through
1657 * which notifications that indicate a lookup service has been discovered
1658 * or discarded will be received. At a minimum, the object supplied
1659 * (through the <code>discoveryMgr</code> parameter) must satisfy the
1660 * contract defined in the <code>DiscoveryManagement</code> interface.
1661 * That is, the object supplied must provide this utility with the ability
1662 * to set discovery listeners and to discard previously discovered
1663 * lookup services when they are found to be unavailable. A value of
1664 * <code>null</code> may be input to the <code>discoveryMgr</code>
1665 * parameter. When <code>null</code> is input to that parameter, an
1666 * instance of <code>LookupDiscoveryManager</code> is used to listen
1667 * for events announcing the discovery of only those lookup services
1668 * that are members of the public group.
1669 * <p>
1670 * The object input to the <code>leaseMgr</code> parameter provides for
1671 * the coordination, systematic renewal, and overall management of all
1672 * leases on the given service reference's residency in the lookup
1673 * services that have been joined. As with the <code>discoveryMgr</code>
1674 * parameter, a value of <code>null</code> may be input to this
1675 * parameter. When <code>null</code> is input to this parameter,
1676 * an instance of <code>LeaseRenewalManager</code>, initially managing
1677 * no <code>Lease</code> objects will be used. This feature allows a
1678 * service to either use a single entity to manage all of its leases,
1679 * or to use separate entities: one to manage the leases unrelated to
1680 * the join process, and one to manage the leases that result from the
1681 * join process, that are accessible only within the current instance
1682 * of the <code>JoinManager</code>.
1683 *
1684 * @param serviceProxy the service reference (proxy) to register with all
1685 * discovered lookup services
1686 * @param attrSets array of <code>Entry</code> consisting of the
1687 * attribute sets with which to register the service
1688 * @param callback reference to the object that should receive the
1689 * event containing the service ID, assigned to the
1690 * service by the first lookup service with which the
1691 * service reference is registered
1692 * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1693 * object this class should use to manage lookup
1694 * service discovery on behalf of the given service
1695 * @param leaseMgr reference to the <code>LeaseRenewalManager</code>
1696 * object this class should use to manage the leases
1697 * on the given service's residency in the lookup
1698 * services that have been joined
1699 *
1700 * @throws java.lang.IllegalArgumentException if the object input to the
1701 * <code>serviceProxy</code> parameter is not serializable
1702 * @throws java.lang.NullPointerException if either <code>null</code> is
1703 * input to the <code>serviceProxy</code> parameter, or at least
1704 * one of the elements of the <code>attrSets</code> parameter is
1705 * <code>null</code>
1706 * @throws java.io.IOException if initiation of discovery process results
1707 * in <code>IOException</code> when socket allocation occurs
1708 *
1709 * @throws java.lang.IllegalStateException if this method is called on
1710 * a terminated <code>JoinManager</code> instance. Note that this
1711 * exception is implementation-specific.
1712 *
1713 * @see net.jini.lookup.ServiceIDListener
1714 * @see net.jini.discovery.DiscoveryManagement
1715 * @see net.jini.discovery.LookupDiscoveryManager
1716 * @see net.jini.lease.LeaseRenewalManager
1717 */
1718 public JoinManager(Object serviceProxy,
1719 Entry[] attrSets,
1720 ServiceIDListener callback,
1721 DiscoveryManagement discoveryMgr,
1722 LeaseRenewalManager leaseMgr) throws IOException
1723 {
1724 this(serviceProxy, attrSets, null, callback,
1725 getConf(EmptyConfiguration.INSTANCE, leaseMgr, discoveryMgr, serviceProxy));
1726 }//end constructor
1727
1728 /**
1729 * Constructs an instance of this class, configured using the items
1730 * retrieved through the given <code>Configuration</code> object,
1731 * that will register the given service reference with all discovered
1732 * lookup services and, through an event sent to the given
1733 * <code>ServiceIDListener</code> object, communicate the service ID
1734 * assigned by the first lookup service with which the service is
1735 * registered. This constructor is typically used by services which
1736 * have not yet been assigned a service ID, and which wish to allow
1737 * for deployment-time configuration of the service's join processing.
1738 * <p>
1739 * The items used to configure the current instance of this class
1740 * are obtained through the object input to the <code>config</code>
1741 * parameter. If <code>null</code> is input to that parameter, a
1742 * <code>NullPointerException</code> is thrown.
1743 * <p>
1744 * The object this utility will use to manage lookup service discovery on
1745 * behalf of the given service can be supplied through either the
1746 * <code>discoveryMgr</code> parameter or through an entry contained
1747 * in the given <code>Configuration</code>. If <code>null</code> is input
1748 * to the <code>discoveryMgr</code> parameter, an attempt will first be
1749 * made to retrieve from the given <code>Configuration</code>, an entry
1750 * named "discoveryManager" (described above). If such an object is
1751 * successfully retrieved from the given <code>Configuration</code>, that
1752 * object will be used to perform the lookup service discovery management
1753 * required by this utility.
1754 * <p>
1755 * If <code>null</code> is input to the <code>discoveryMgr</code>
1756 * parameter, and no entry named "discoveryManager" is specified in the
1757 * given <code>Configuration</code>, then an instance of the utility class
1758 * <code>LookupDiscoveryManager</code> will be used to listen for events
1759 * announcing the discovery of only those lookup services that are
1760 * members of the public group.
1761 * <p>
1762 * As with the <code>discoveryMgr</code> parameter, the object this
1763 * utility will use to perform lease management on behalf of the given
1764 * service can be supplied through either the <code>leaseMgr</code>
1765 * parameter or through an entry contained in the given
1766 * <code>Configuration</code>. If <code>null</code> is input to the
1767 * <code>leaseMgr</code> parameter, an attempt will first be made to
1768 * retrieve from the given <code>Configuration</code>, an entry named
1769 * "leaseManager" (described above). If such an object is successfully
1770 * retrieved from the given <code>Configuration</code>, that object
1771 * will be used to perform the lease management required by this utility.
1772 * <p>
1773 * If <code>null</code> is input to the <code>leaseMgr</code>
1774 * parameter, and no entry named "leaseManager" is specified in the
1775 * given <code>Configuration</code>, then an instance of the utility
1776 * class <code>LeaseRenewalManager</code> that takes the given
1777 * <code>Configuration</code> will be created (initially managing no
1778 * leases) and used to perform all required lease renewal management
1779 * on behalf of the given service.
1780 * <p>
1781 * Except for the <code>config</code> parameter and the additional
1782 * semantics imposed by that parameter (as noted above), all other
1783 * parameters of this form of the constructor, along with their
1784 * associated semantics, are identical to that of the five-argument
1785 * constructor that takes a <code>ServiceIDListener</code>.
1786 *
1787 * @param serviceProxy the service reference (proxy) to register with all
1788 * discovered lookup services
1789 * @param attrSets array of <code>Entry</code> consisting of the
1790 * attribute sets with which to register the service
1791 * @param callback reference to the <code>ServiceIDListener</code>
1792 * object that should receive the event containing the
1793 * service ID assigned to the service by the first
1794 * lookup service with which the service reference
1795 * is registered
1796 * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1797 * object this class should use to manage lookup
1798 * service discovery on behalf of the given service
1799 * @param leaseMgr reference to the <code>LeaseRenewalManager</code>
1800 * object this class should use to manage the leases
1801 * on the given service's residency in the lookup
1802 * services that have been joined
1803 * @param config instance of <code>Configuration</code> through
1804 * which the items used to configure the current
1805 * instance of this class are obtained
1806 *
1807 * @throws java.lang.IllegalArgumentException if the object input to the
1808 * <code>serviceProxy</code> parameter is not serializable
1809 * @throws java.lang.NullPointerException if <code>null</code> is input
1810 * to the <code>serviceProxy</code> parameter or the
1811 * <code>config</code> parameter, or if at least one of the
1812 * elements of the <code>attrSets</code> parameter is
1813 * <code>null</code>
1814 * @throws java.io.IOException if initiation of discovery process results
1815 * in <code>IOException</code> when socket allocation occurs
1816 * @throws net.jini.config.ConfigurationException if an exception
1817 * occurs while retrieving an item from the given
1818 * <code>Configuration</code> object
1819 *
1820 * @throws java.lang.IllegalStateException if this method is called on
1821 * a terminated <code>JoinManager</code> instance. Note that this
1822 * exception is implementation-specific.
1823 *
1824 * @see net.jini.lookup.ServiceIDListener
1825 * @see net.jini.discovery.DiscoveryManagement
1826 * @see net.jini.discovery.LookupDiscoveryManager
1827 * @see net.jini.lease.LeaseRenewalManager
1828 * @see net.jini.config.Configuration
1829 * @see net.jini.config.ConfigurationException
1830 */
1831 public JoinManager(Object serviceProxy,
1832 Entry[] attrSets,
1833 ServiceIDListener callback,
1834 DiscoveryManagement discoveryMgr,
1835 LeaseRenewalManager leaseMgr,
1836 Configuration config)
1837 throws IOException, ConfigurationException
1838 {
1839
1840 this(serviceProxy, attrSets, null, callback,
1841 getConfig(config, leaseMgr, discoveryMgr, serviceProxy)
1842 );
1843 }//end constructor
1844
1845 /**
1846 * Constructs an instance of this class that will register the
1847 * service with all discovered lookup services, using the supplied
1848 * <code>ServiceID</code>. This constructor is typically used by
1849 * services which have already been assigned a service ID (possibly
1850 * by the service provider itself or as a result of a prior registration
1851 * with some lookup service), and which do not wish to allow for
1852 * deployment-time configuration of the service's join processing.
1853 * <p>
1854 * Except that the desired <code>ServiceID</code> is supplied through the
1855 * <code>serviceID</code> parameter rather than through a notification
1856 * sent to a <code>ServiceIDListener</code>, all other parameters
1857 * of this form of the constructor, along with their associated semantics,
1858 * are identical to that of the five-argument constructor that takes
1859 * a <code>ServiceIDListener</code>.
1860 *
1861 * @param serviceProxy a reference to the service requesting the services
1862 * of this class
1863 * @param attrSets array of <code>Entry</code> consisting of the
1864 * attribute sets with which to register the service
1865 * @param serviceID an instance of <code>ServiceID</code> with which to
1866 * register the service with all desired lookup
1867 * services
1868 * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1869 * object this class should use to manage the given
1870 * service's lookup service discovery duties
1871 * @param leaseMgr reference to the <code>LeaseRenewalManager</code>
1872 * object this class should use to manage the leases
1873 * on the given service's residency in the lookup
1874 * services that have been joined
1875 *
1876 * @throws java.lang.IllegalArgumentException if the object input to the
1877 * <code>serviceProxy</code> parameter is not serializable
1878 * @throws java.lang.NullPointerException if either <code>null</code> is
1879 * input to the <code>serviceProxy</code> parameter, or at least
1880 * one of the elements of the <code>attrSets</code> parameter is
1881 * <code>null</code>
1882 * @throws java.io.IOException if initiation of discovery process results
1883 * in <code>IOException</code> when socket allocation occurs
1884 *
1885 * @throws java.lang.IllegalStateException if this method is called on
1886 * a terminated <code>JoinManager</code> instance. Note that this
1887 * exception is implementation-specific.
1888 *
1889 * @see net.jini.core.lookup.ServiceID
1890 * @see net.jini.discovery.DiscoveryManagement
1891 * @see net.jini.discovery.LookupDiscoveryManager
1892 * @see net.jini.lease.LeaseRenewalManager
1893 */
1894 public JoinManager(Object serviceProxy,
1895 Entry[] attrSets,
1896 ServiceID serviceID,
1897 DiscoveryManagement discoveryMgr,
1898 LeaseRenewalManager leaseMgr) throws IOException
1899 {
1900 this(serviceProxy, attrSets, serviceID, null,
1901 getConf(EmptyConfiguration.INSTANCE, leaseMgr, discoveryMgr, serviceProxy)
1902 );
1903 }//end constructor
1904
1905 /**
1906 * Constructs an instance of this class, configured using the items
1907 * retrieved through the given <code>Configuration</code>, that will
1908 * register the service with all discovered lookup services, using the
1909 * supplied <code>ServiceID</code>. This constructor is typically used by
1910 * services which have already been assigned a service ID (possibly
1911 * by the service provider itself or as a result of a prior registration
1912 * with some lookup service), and which wish to allow for deployment-time
1913 * configuration of the service's join processing.
1914 * <p>
1915 * The items used to configure the current instance of this class
1916 * are obtained through the object input to the <code>config</code>
1917 * parameter. If <code>null</code> is input to that parameter, a
1918 * <code>NullPointerException</code> is thrown.
1919 * <p>
1920 * Except that the desired <code>ServiceID</code> is supplied through the
1921 * <code>serviceID</code> parameter rather than through a notification
1922 * sent to a <code>ServiceIDListener</code>, all other parameters
1923 * of this form of the constructor, along with their associated semantics,
1924 * are identical to that of the six-argument constructor that takes
1925 * a <code>ServiceIDListener</code>.
1926 *
1927 * @param serviceProxy a reference to the service requesting the services
1928 * of this class
1929 * @param attrSets array of <code>Entry</code> consisting of the
1930 * attribute sets with which to register the service.
1931 * @param serviceID an instance of <code>ServiceID</code> with which to
1932 * register the service with all desired lookup
1933 * services
1934 * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1935 * object this class should use to manage lookup
1936 * service discovery on behalf of the given service
1937 * @param leaseMgr reference to the <code>LeaseRenewalManager</code>
1938 * object this class should use to manage the leases
1939 * on the given service's residency in the lookup
1940 * services that have been joined
1941 * @param config instance of <code>Configuration</code> through
1942 * which the items used to configure the current
1943 * instance of this class are obtained
1944 *
1945 * @throws java.lang.IllegalArgumentException if the object input to the
1946 * <code>serviceProxy</code> parameter is not serializable
1947 * @throws java.lang.NullPointerException if <code>null</code> is input
1948 * to the <code>serviceProxy</code> parameter or the
1949 * <code>config</code> parameter, or if at least one of the
1950 * elements of the <code>attrSets</code> parameter is
1951 * <code>null</code>
1952 * @throws java.io.IOException if initiation of discovery process results
1953 * in <code>IOException</code> when socket allocation occurs
1954 * @throws net.jini.config.ConfigurationException if an exception
1955 * occurs while retrieving an item from the given
1956 * <code>Configuration</code> object
1957 *
1958 * @throws java.lang.IllegalStateException if this method is called on
1959 * a terminated <code>JoinManager</code> instance. Note that this
1960 * exception is implementation-specific.
1961 *
1962 * @see net.jini.core.lookup.ServiceID
1963 * @see net.jini.discovery.DiscoveryManagement
1964 * @see net.jini.discovery.LookupDiscoveryManager
1965 * @see net.jini.lease.LeaseRenewalManager
1966 * @see net.jini.config.Configuration
1967 * @see net.jini.config.ConfigurationException
1968 */
1969 public JoinManager(Object serviceProxy,
1970 Entry[] attrSets,
1971 ServiceID serviceID,
1972 DiscoveryManagement discoveryMgr,
1973 LeaseRenewalManager leaseMgr,
1974 Configuration config)
1975 throws IOException, ConfigurationException
1976 {
1977 this(serviceProxy, attrSets, serviceID, null,
1978 getConfig(config, leaseMgr, discoveryMgr, serviceProxy)
1979 );
1980 }//end constructor
1981
1982 /**
1983 * Returns the instance of <code>DiscoveryManagement</code> that was
1984 * either passed into the constructor, or that was created as a result
1985 * of <code>null</code> being input to that parameter.
1986 * <p>
1987 * The object returned by this method encapsulates the mechanism by which
1988 * either the <code>JoinManager</code> or the entity itself can set
1989 * discovery listeners and discard previously discovered lookup services
1990 * when they are found to be unavailable.
1991 *
1992 * @return the instance of the <code>DiscoveryManagement</code> interface
1993 * that was either passed into the constructor, or that was
1994 * created as a result of <code>null</code> being input to that
1995 * parameter.
1996 *
1997 * @see net.jini.discovery.DiscoveryManagement
1998 * @see net.jini.discovery.LookupDiscoveryManager
1999 */
2000 public DiscoveryManagement getDiscoveryManager(){
2001 if(bTerminated)
2002 throw new IllegalStateException("join manager was terminated");
2003 return discMgr;
2004 }//end getDiscoveryManager
2005
2006 /**
2007 * Returns the instance of the <code>LeaseRenewalManager</code> class
2008 * that was either passed into the constructor, or that was created
2009 * as a result of <code>null</code> being input to that parameter.
2010 * <p>
2011 * The object returned by this method manages the leases requested and
2012 * held by the <code>JoinManager</code>. Although it may also manage
2013 * leases unrelated to the join process that are requested and held by
2014 * the service itself, the leases with which the <code>JoinManager</code>
2015 * is concerned are the leases that correspond to the service registration
2016 * requests made with each lookup service the service wishes to join.
2017 *
2018 * @return the instance of the <code>LeaseRenewalManager</code> class
2019 * that was either passed into the constructor, or that was
2020 * created as a result of <code>null</code> being input to that
2021 * parameter.
2022 *
2023 * @see net.jini.discovery.DiscoveryManagement
2024 * @see net.jini.lease.LeaseRenewalManager
2025 */
2026 public LeaseRenewalManager getLeaseRenewalManager(){
2027 if(bTerminated)
2028 throw new IllegalStateException("join manager was terminated");
2029 return leaseRenewalMgr;
2030 }//end getLeaseRenewalManager
2031
2032 /**
2033 * Returns an array of <code>ServiceRegistrar</code> objects, each
2034 * corresponding to a lookup service with which the service is currently
2035 * registered (joined). If there are no lookup services with which the
2036 * service is currently registered, this method returns the empty array.
2037 * This method returns a new array upon each invocation.
2038 *
2039 * @return array of instances of <code>ServiceRegistrar</code>, each
2040 * corresponding to a lookup service with which the service is
2041 * currently registered
2042 *
2043 * @see net.jini.core.lookup.ServiceRegistrar
2044 */
2045 public ServiceRegistrar[] getJoinSet() {
2046 if(bTerminated) throw new IllegalStateException("join manager was terminated");
2047 List<ServiceRegistrar> retList = new LinkedList<ServiceRegistrar>();
2048 for (Iterator<ProxyReg> iter = joinSet.iterator(); iter.hasNext(); ) {
2049 ProxyReg proxyReg = iter.next();
2050 if(proxyReg.srvcRegistration != null) {//test registration flag
2051 retList.add(proxyReg.proxy);
2052 }//endif
2053 }//end loop
2054 return ( (retList.toArray(new ServiceRegistrar[retList.size()]) ) );
2055 }//end getJoinSet
2056
2057 /**
2058 * Returns an array containing the set of attributes currently associated
2059 * with the service. If the service is not currently associated with an
2060 * attribute set, this method returns the empty array. This method returns
2061 * a new array upon each invocation.
2062 *
2063 * @return array of instances of <code>Entry</code> consisting of the
2064 * set of attributes with which the service is registered in
2065 * each lookup service that it has joined
2066 *
2067 * @see net.jini.core.entry.Entry
2068 * @see #setAttributes
2069 */
2070 public Entry[] getAttributes() {
2071 if(bTerminated) throw new IllegalStateException("join manager was terminated");
2072 Entry[] result = lookupAttr.clone();
2073 for (int i = 0, l = result.length; i < l; i++){
2074 if (result[i] instanceof CloneableEntry){
2075 result[i] = ((CloneableEntry)result[i]).clone();
2076 }
2077 }
2078 return result;
2079 }//end getAttributes
2080
2081 /**
2082 * Associates a new set of attributes with the service, in addition to
2083 * the service's current set of attributes. The association of this new
2084 * set of attributes with the service will be propagated to each lookup
2085 * service with which the service is registered. Note that this
2086 * propagation is performed asynchronously, thus there is no guarantee
2087 * that the propagation of the attributes to all lookup services with
2088 * which the service is registered will have completed upon return from
2089 * this method.
2090 * <p>
2091 * An invocation of this method with duplicate elements in the
2092 * <code>attrSets</code> parameter (where duplication means attribute
2093 * equality as defined by calling the <code>MarshalledObject.equals</code>
2094 * method on field values) is equivalent to performing the invocation
2095 * with the duplicates removed from that parameter.
2096 * <p>
2097 * Note that because there is no guarantee that attribute propagation
2098 * will have completed upon return from this method, services that
2099 * invoke this method must take care not to modify the contents of the
2100 * <code>attrSets</code> parameter. Doing so could cause the service's
2101 * attribute state to be corrupted or inconsistent on a subset of the
2102 * lookup services with which the service is registered as compared with
2103 * the state reflected on the remaining lookup services. It is for this
2104 * reason that the effects of modifying the contents of the
2105 * <code>attrSets</code> parameter, after this method is invoked, are
2106 * undefined.
2107 *
2108 * @param attrSets array of <code>Entry</code> consisting of the
2109 * attribute sets with which to augment the service's
2110 * current set of attributes
2111 *
2112 * @throws java.lang.NullPointerException if either <code>null</code> is
2113 * input to the <code>attrSets</code> parameter, or one or more
2114 * of the elements of the <code>attrSets</code> parameter is
2115 * <code>null</code>
2116 *
2117 * @see net.jini.core.entry.Entry
2118 */
2119 public void addAttributes(Entry[] attrSets) {
2120 addAttributes(attrSets, false);
2121 }//end addAttributes
2122
2123 /**
2124 * Associates a new set of attributes with the service, in addition to
2125 * the service's current set of attributes. The association of this new
2126 * set of attributes with the service will be propagated to each lookup
2127 * service with which the service is registered. Note that this
2128 * propagation is performed asynchronously, thus there is no guarantee
2129 * that the propagation of the attributes to all lookup services with
2130 * which the service is registered will have completed upon return from
2131 * this method.
2132 * <p>
2133 * An invocation of this method with duplicate elements in the
2134 * <code>attrSets</code> parameter (where duplication means attribute
2135 * equality as defined by calling the <code>MarshalledObject.equals</code>
2136 * method on field values) is equivalent to performing the invocation
2137 * with the duplicates removed from that parameter.
2138 * <p>
2139 * Note that because there is no guarantee that attribute propagation
2140 * will have completed upon return from this method, services that
2141 * invoke this method must take care not to modify the contents of the
2142 * <code>attrSets</code> parameter. Doing so could cause the service's
2143 * attribute state to be corrupted or inconsistent on a subset of the
2144 * lookup services with which the service is registered as compared with
2145 * the state reflected on the remaining lookup services. It is for this
2146 * reason that the effects of modifying the contents of the
2147 * <code>attrSets</code> parameter, after this method is invoked, are
2148 * undefined.
2149 * <p>
2150 * A service typically employs this version of <code>addAttributes</code>
2151 * to prevent clients or other services from attempting to add what are
2152 * referred to as "service controlled attributes" to the service's set.
2153 * A service controlled attribute is an attribute that implements the
2154 * <code>ServiceControlled</code> marker interface.
2155 * <p>
2156 * Consider a printer service. With printers, there are often times error
2157 * conditions, that only the printer can detect (for example, a paper
2158 * jam or a toner low condition). To report conditions such as these to
2159 * interested parties, the printer typically adds an attribute to its
2160 * attribute set, resulting in an event being sent that notifies clients
2161 * that have registered interest in such events. When the condition is
2162 * corrected, the printer would then remove the attribute from its set
2163 * by invoking the <code>modifyAttributes</code> method in the appropriate
2164 * manner.
2165 * <p>
2166 * Attributes representing conditions that only the service can know about
2167 * or control are good candidates for being defined as service controlled
2168 * attributes. That is, the service provider (the developer of the printer
2169 * service for example) would define the attributes that represent
2170 * conditions such as those just described to implement the
2171 * <code>ServiceControlled</code> marker interface. Thus, when other
2172 * entities attempt to add new attributes, services that wish to employ
2173 * such service controlled attributes should ultimately invoke only this
2174 * version of <code>addAttributes</code> (with the <code>checkSC</code>
2175 * parameter set to <code>true</code>), resulting in a
2176 * <code>SecurityException</code> if any of the attributes being added
2177 * happen to be service controlled attributes. In this way, only the
2178 * printer itself would be able to set a "paper jammed" or "toner low"
2179 * attribute, not some arbitrary client.
2180 *
2181 * @param attrSets array of <code>Entry</code> consisting of the
2182 * attribute sets with which to augment the service's
2183 * current set of attributes
2184 * @param checkSC <code>boolean</code> flag indicating whether the
2185 * elements of the set of attributes to add should be
2186 * checked to determine if they are service controlled
2187 * attributes
2188 *
2189 * @throws java.lang.NullPointerException if either <code>null</code> is
2190 * input to the <code>attrSets</code> parameter, or one or more
2191 * of the elements of the <code>attrSets</code> parameter is
2192 * <code>null</code>
2193 *
2194 * @throws java.lang.SecurityException if the <code>checkSC</code>
2195 * parameter is <code>true</code>, and at least one of the
2196 * attributes to be added is an instance of the
2197 * <code>ServiceControlled</code> marker interface
2198 *
2199 * @see net.jini.core.entry.Entry
2200 * @see net.jini.lookup.entry.ServiceControlled
2201 */
2202 public void addAttributes(Entry[] attrSets, boolean checkSC) {
2203 if(bTerminated) throw new IllegalStateException("join manager was terminated");
2204 synchronized(this) {
2205 lookupAttr = LookupAttributes.add(lookupAttr, attrSets, checkSC);
2206 serviceItem = new ServiceItem(serviceItem.serviceID, serviceItem.service, lookupAttr);
2207 }
2208 Iterator<ProxyReg> it = joinSet.iterator();
2209 while (it.hasNext()){
2210 ProxyReg proxyReg = it.next();
2211 proxyReg.addTask(new AddAttributesTask(proxyReg,attrSets));
2212 }//end loop
2213 }//end addAttributes
2214
2215 /**
2216 * Replaces the service's current set of attributes with a new set of
2217 * attributes. The association of this new set of attributes with the
2218 * service will be propagated to each lookup service with which the
2219 * service is registered. Note that this propagation is performed
2220 * asynchronously, thus there is no guarantee that the propagation of
2221 * the attributes to all lookup services with which the service is
2222 * registered will have completed upon return from this method.
2223 * <p>
2224 * An invocation of this method with duplicate elements in the
2225 * <code>attrSets</code> parameter (where duplication means attribute
2226 * equality as defined by calling the <code>MarshalledObject.equals</code>
2227 * method on field values) is equivalent to performing the invocation
2228 * with the duplicates removed from that parameter.
2229 * <p>
2230 * Note that because there is no guarantee that attribute propagation
2231 * will have completed upon return from this method, services that
2232 * invoke this method must take care not to modify the contents of the
2233 * <code>attrSets</code> parameter. Doing so could cause the service's
2234 * attribute state to be corrupted or inconsistent on a subset of the
2235 * lookup services with which the service is registered as compared with
2236 * the state reflected on the remaining lookup services. It is for this
2237 * reason that the effects of modifying the contents of the
2238 * <code>attrSets</code> parameter, after this method is invoked, are
2239 * undefined.
2240 *
2241 * @param attrSets array of <code>Entry</code> consisting of the
2242 * attribute sets with which to replace the service's
2243 * current set of attributes
2244 *
2245 * @throws java.lang.NullPointerException if either <code>null</code> is
2246 * input to the <code>attrSets</code> parameter, or one or more
2247 * of the elements of the <code>attrSets</code> parameter is
2248 * <code>null</code>.
2249 *
2250 * @see net.jini.core.entry.Entry
2251 * @see #getAttributes
2252 */
2253 public void setAttributes(Entry[] attrSets) {
2254 if(bTerminated)
2255 throw new IllegalStateException("join manager was terminated");
2256 testForNullElement(attrSets);
2257 synchronized(this) {
2258 lookupAttr = (Entry[]) attrSets.clone();
2259 serviceItem = new ServiceItem(serviceItem.serviceID,
2260 serviceItem.service, lookupAttr);
2261 }
2262 Iterator<ProxyReg> it = joinSet.iterator();
2263 while (it.hasNext()){
2264 ProxyReg proxyReg = it.next();
2265 proxyReg.addTask(new SetAttributesTask(proxyReg,attrSets));
2266 }
2267 }//end setAttributes
2268
2269 /**
2270 * Changes the service's current set of attributes using the same
2271 * semantics as the <code>modifyAttributes</code> method of the
2272 * <code>ServiceRegistration</code> class.
2273 * <p>
2274 * The association of the new set of attributes with the service will
2275 * be propagated to each lookup service with which the service is
2276 * registered. Note that this propagation is performed asynchronously,
2277 * thus there is no guarantee that the propagation of the attributes to
2278 * all lookup services with which the service is registered will have
2279 * completed upon return from this method.
2280 * <p>
2281 * Note that if the length of the array containing the templates does
2282 * not equal the length of the array containing the modifications, an
2283 * <code>IllegalArgumentException</code> will be thrown and propagated
2284 * through this method.
2285 * <p>
2286 * Note also that because there is no guarantee that attribute propagation
2287 * will have completed upon return from this method, services that
2288 * invoke this method must take care not to modify the contents of the
2289 * <code>attrSets</code> parameter. Doing so could cause the service's
2290 * attribute state to be corrupted or inconsistent on a subset of the
2291 * lookup services with which the service is registered as compared with
2292 * the state reflected on the remaining lookup services. It is for this
2293 * reason that the effects of modifying the contents of the
2294 * <code>attrSets</code> parameter, after this method is invoked, are
2295 * undefined.
2296 *
2297 * @param attrSetTemplates array of <code>Entry</code> used to identify
2298 * which elements to modify from the service's
2299 * current set of attributes
2300 * @param attrSets array of <code>Entry</code> containing the
2301 * actual modifications to make in the matching
2302 * sets found using the
2303 * <code>attrSetTemplates</code> parameter
2304 *
2305 * @throws java.lang.IllegalArgumentException if the array containing the
2306 * templates does not equal the length of the array containing the
2307 * modifications
2308 *
2309 * @see net.jini.core.entry.Entry
2310 * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
2311 */
2312 public void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets) {
2313 modifyAttributes(attrSetTemplates, attrSets, false);
2314 }//end modifyAttributes
2315
2316 /**
2317 * Changes the service's current set of attributes using the same
2318 * semantics as the <code>modifyAttributes</code> method of the
2319 * <code>ServiceRegistration</code> class.
2320 * <p>
2321 * The association of the new set of attributes with the service will
2322 * be propagated to each lookup service with which the service is
2323 * registered. Note that this propagation is performed asynchronously,
2324 * thus there is no guarantee that the propagation of the attributes to
2325 * all lookup services with which the service is registered will have
2326 * completed upon return from this method.
2327 * <p>
2328 * Note that if the length of the array containing the templates does
2329 * not equal the length of the array containing the modifications, an
2330 * <code>IllegalArgumentException</code> will be thrown and propagated
2331 * through this method.
2332 * <p>
2333 * Note also that because there is no guarantee that attribute propagation
2334 * will have completed upon return from this method, services that
2335 * invoke this method must take care not to modify the contents of the
2336 * <code>attrSets</code> parameter. Doing so could cause the service's
2337 * attribute state to be corrupted or inconsistent on a subset of the
2338 * lookup services with which the service is registered as compared with
2339 * the state reflected on the remaining lookup services. It is for this
2340 * reason that the effects of modifying the contents of the
2341 * <code>attrSets</code> parameter, after this method is invoked, are
2342 * undefined.
2343 * <p>
2344 * A service typically employs this version of
2345 * <code>modifyAttributes</code> to prevent clients or other services
2346 * from attempting to modify what are referred to as "service controlled
2347 * attributes" in the service's set. A service controlled attribute is an
2348 * attribute that implements the <code>ServiceControlled</code> marker
2349 * interface.
2350 * <p>
2351 * Attributes representing conditions that only the service can know about
2352 * or control are good candidates for being defined as service controlled
2353 * attributes. When other entities attempt to modify a service's
2354 * attributes, if the service wishes to employ such service controlled
2355 * attributes, the service should ultimately invoke only this version
2356 * of <code>modifyAttributes</code> (with the <code>checkSC</code>
2357 * parameter set to <code>true</code>), resulting in a
2358 * <code>SecurityException</code> if any of the attributes being modified
2359 * happen to be service controlled attributes.
2360 *
2361 * @param attrSetTemplates array of <code>Entry</code> used to identify
2362 * which elements to modify from the service's
2363 * current set of attributes
2364 * @param attrSets array of <code>Entry</code> containing the
2365 * actual modifications to make in the matching
2366 * sets found using the
2367 * <code>attrSetTemplates</code> parameter
2368 * @param checkSC <code>boolean</code> flag indicating whether the
2369 * elements of the set of attributes to modify
2370 * should be checked to determine if they are
2371 * service controlled attributes
2372 *
2373 * @throws java.lang.IllegalArgumentException if the array containing the
2374 * templates does not equal the length of the array containing
2375 * the modifications
2376 *
2377 * @throws java.lang.SecurityException if the <code>checkSC</code>
2378 * parameter is <code>true</code>, and at least one of the
2379 * attributes to be modified is an instance of the
2380 * <code>ServiceControlled</code> marker interface
2381 *
2382 * @see net.jini.core.entry.Entry
2383 * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
2384 * @see net.jini.lookup.entry.ServiceControlled
2385 */
2386 public void modifyAttributes(Entry[] attrSetTemplates,
2387 Entry[] attrSets,
2388 boolean checkSC)
2389 {
2390 if(bTerminated)
2391 throw new IllegalStateException("join manager was terminated");
2392 synchronized(this) {
2393 lookupAttr = LookupAttributes.modify(lookupAttr, attrSetTemplates,
2394 attrSets, checkSC);
2395 serviceItem = new ServiceItem(serviceItem.serviceID,
2396 serviceItem.service, lookupAttr);
2397 }//end sync
2398 Iterator<ProxyReg> it = joinSet.iterator();
2399 while (it.hasNext()){
2400 ProxyReg proxyReg = it.next();
2401 proxyReg.addTask(new ModifyAttributesTask(proxyReg,
2402 attrSetTemplates,
2403 attrSets));
2404 }//end loop
2405 }//end modifyAttributes
2406
2407 /**
2408 * Performs cleanup duties related to the termination of the lookup
2409 * service discovery event mechanism, as well as the lease and
2410 * thread management performed by the <code>JoinManager</code>. This
2411 * method will cancel all of the service's managed leases that were
2412 * granted by the lookup services with which the service is registered,
2413 * and will terminate all threads that have been created.
2414 * <p>
2415 * Note that if the discovery manager employed by the instance of this
2416 * class that is being terminated was created by the instance itself,
2417 * this method will terminate all discovery processing being performed by
2418 * that manager object on behalf of the service; otherwise, the discovery
2419 * manager supplied by the service is still valid.
2420 * <p>
2421 * Whether an instance of the <code>LeaseRenewalManager</code> class was
2422 * supplied by the service or created by the <code>JoinManager</code>
2423 * itself, any reference to that object obtained by the service prior to
2424 * termination will still be valid after termination.
2425 * Note also this class makes certain concurrency guarantees with respect
2426 * to an invocation of the terminate method while other method invocations
2427 * are in progress. The termination process will not begin until
2428 * completion of all invocations of the methods defined in the public
2429 * interface of this class. Furthermore, once the termination process has
2430 * begun, no further remote method invocations will be made by this class,
2431 * and all other method invocations made on this class will not return
2432 * until the termination process has completed.
2433 * <p>
2434 * Upon completion of the termination process, the semantics of all
2435 * current and future method invocations on the instance of this class
2436 * that was just terminated are undefined; although the reference to the
2437 * <code>LeaseRenewalManager</code> object employed by that instance
2438 * of <code>JoinManager</code> is still valid.
2439 */
2440 public void terminate() {
2441 synchronized(this) {
2442 if(bTerminated) return;//allow for multiple terminations
2443 bTerminated = true;
2444 }//end sync(this)
2445 /* Terminate discovery and task management */
2446 discMgr.removeDiscoveryListener(discMgrListener);
2447 if(bCreateDiscMgr) discMgr.terminate();
2448 terminateTaskMgr();
2449 /* Clear the joinSet and cancel all leases held by the service */
2450 Iterator<ProxyReg> iter = joinSet.iterator();
2451 while (iter.hasNext()) {
2452 try {
2453 leaseRenewalMgr.cancel(iter.next().serviceLease );
2454 } catch (UnknownLeaseException e){
2455 } catch (RemoteException e) {}
2456 }//end loop
2457 leaseRenewalMgr.close();
2458 joinSet.clear();
2459 }//end terminate
2460
2461 /**
2462 * Registers a new reference to the service with all current and future
2463 * discovered lookup services. The new service reference will replace
2464 * the reference that was previously registered as a result of either
2465 * constructing this utility, or a prior invocation of one of the forms
2466 * of this method. The new service reference will be registered using
2467 * the same <code>ServiceID</code> with which previous registrations
2468 * were made through this utility.
2469 * <p>
2470 * The value input to the <code>serviceProxy</code> parameter represents
2471 * the new service reference (proxy) to register with each discovered
2472 * lookup service. If the <code>Object</code> input to that parameter is
2473 * not <code>Serializable</code>, an <code>IllegalArgumentException</code>
2474 * is thrown. If <code>null</code> is input to that parameter, a
2475 * <code>NullPointerException</code> is thrown.
2476 * <p>
2477 * The attribute sets that this method associates with the new service
2478 * reference are the same attribute sets as those associated with the
2479 * old registration.
2480 *
2481 * @param serviceProxy the new service reference (proxy) to register with
2482 * all current and future discovered lookup services
2483 *
2484 * @throws java.lang.IllegalArgumentException if the object input to the
2485 * <code>serviceProxy</code> parameter is not serializable
2486 * @throws java.lang.NullPointerException if <code>null</code> is input
2487 * to the <code>serviceProxy</code> parameter
2488 *
2489 * @throws java.lang.IllegalStateException if this method is called on
2490 * a terminated <code>JoinManager</code> instance. Note that this
2491 * exception is implementation-specific.
2492 */
2493 public void replaceRegistration(Object serviceProxy) {
2494 replaceRegistrationDo(serviceProxy, null, false);
2495 }//end replaceRegistration
2496
2497 /**
2498 * Registers a new reference to the service with all current and future
2499 * discovered lookup services, applying semantics identical to the
2500 * one-argument form of this method, except with respect to the
2501 * registration of the given attribute sets.
2502 * <p>
2503 * This form of <code>replaceRegistration</code> takes as its
2504 * second parameter, an array of <code>Entry</code> objects
2505 * (<code>attrSets</code>), none of whose elements may be
2506 * <code>null</code>, that represents the new set of attributes to
2507 * associate with the new service reference to be registered. As with
2508 * the constructor to this utility, passing <code>null</code> as the
2509 * value of the <code>attrSets</code> parameter is equivalent to passing
2510 * an empty array. If any of the elements of <code>attrSets</code> are
2511 * <code>null</code>, a <code>NullPointerException</code> is thrown.
2512 * This new set of attributes will be associated with the service in
2513 * all future join processing.
2514 *
2515 * @param serviceProxy the new service reference (proxy) to register with
2516 * all current and future discovered lookup services
2517 * @param attrSets array of <code>Entry</code> consisting of the
2518 * attribute sets with which to register the new
2519 * service reference. Passing <code>null</code> as
2520 * the value of this parameter is equivalent to
2521 * passing an empty <code>Entry</code> array
2522 *
2523 * @throws java.lang.IllegalArgumentException if the object input to the
2524 * <code>serviceProxy</code> parameter is not serializable
2525 * @throws java.lang.NullPointerException if either <code>null</code> is
2526 * input to the <code>serviceProxy</code> parameter, or at least
2527 * one of the elements of the <code>attrSets</code> parameter is
2528 * <code>null</code>
2529 *
2530 * @throws java.lang.IllegalStateException if this method is called on
2531 * a terminated <code>JoinManager</code> instance. Note that this
2532 * exception is implementation-specific.
2533 */
2534 public void replaceRegistration(Object serviceProxy, Entry[] attrSets) {
2535 replaceRegistrationDo(serviceProxy, attrSets, true);
2536 }//end replaceRegistration
2537
2538 private static class Conf{
2539 ProxyPreparer registrarPreparer;
2540 ProxyPreparer registrationPreparer;
2541 ProxyPreparer serviceLeasePreparer;
2542 ExecutorService executorService;
2543 WakeupManager wakeupManager;
2544 Integer maxNretrys;
2545 LeaseRenewalManager leaseRenewalManager;
2546 Long renewalDuration;
2547 DiscoveryManagement discoveryMgr;
2548 boolean bcreateDisco;
2549
2550 Conf ( ProxyPreparer registrarPreparer,
2551 ProxyPreparer registrationPreparer,
2552 ProxyPreparer serviceLeasePreparer,
2553 ExecutorService taskManager,
2554 WakeupManager wakeupManager,
2555 Integer maxNretrys,
2556 LeaseRenewalManager leaseRenewalManager,
2557 Long renewalDuration,
2558 DiscoveryManagement discoveryMgr,
2559 boolean bcreateDisco)
2560 {
2561 this.registrarPreparer = registrarPreparer;
2562 this.registrationPreparer = registrationPreparer;
2563 this.serviceLeasePreparer = serviceLeasePreparer;
2564 this.executorService = taskManager;
2565 this.wakeupManager = wakeupManager;
2566 this.maxNretrys = maxNretrys;
2567 this.leaseRenewalManager = leaseRenewalManager;
2568 this.renewalDuration = renewalDuration;
2569 this.discoveryMgr = discoveryMgr;
2570 this.bcreateDisco = bcreateDisco;
2571 }
2572 }
2573
2574 /**
2575 * This method is for constructors that use an empty configuration.
2576 *
2577 * @param config
2578 * @param leaseMgr
2579 * @param discoveryMgr
2580 * @param serviceProxy
2581 * @return Conf
2582 * @throws IOException
2583 * @throws NullPointerException
2584 * @throws IllegalArgumentException
2585 */
2586 private static Conf getConf( Configuration config,
2587 LeaseRenewalManager leaseMgr,
2588 DiscoveryManagement discoveryMgr,
2589 Object serviceProxy)
2590 throws IOException, NullPointerException, IllegalArgumentException {
2591 try {
2592 return getConfig(config, leaseMgr, discoveryMgr, serviceProxy);
2593 } catch (ConfigurationException e){
2594 throw new IOException("Configuration problem during construction", e);
2595 }
2596 }
2597
2598 /**
2599 * Gets the configuration and throws any exceptions.
2600 *
2601 * This static method guards against finalizer attacks and allows fields
2602 * to be final.
2603 *
2604 * @param config
2605 * @param leaseMgr
2606 * @param discoveryMgr
2607 * @param serviceProxy
2608 * @return Conf
2609 * @throws IOException
2610 * @throws ConfigurationException
2611 * @throws NullPointerException
2612 * @throws IllegalArgumentException
2613 */
2614 private static Conf getConfig( Configuration config,
2615 LeaseRenewalManager leaseMgr,
2616 DiscoveryManagement discoveryMgr,
2617 Object serviceProxy)
2618 throws IOException, ConfigurationException, NullPointerException,
2619 IllegalArgumentException
2620 {
2621 if(!(serviceProxy instanceof java.io.Serializable)) {
2622 throw new IllegalArgumentException
2623 ("serviceProxy must be Serializable");
2624 }//endif
2625 /* Retrieve configuration items if applicable */
2626 if(config == null) throw new NullPointerException("config is null");
2627 /* Proxy preparers */
2628 ProxyPreparer registrarPreparer = config.getEntry
2629 (COMPONENT_NAME,
2630 "registrarPreparer",
2631 ProxyPreparer.class,
2632 new BasicProxyPreparer());
2633 ProxyPreparer registrationPreparer = config.getEntry
2634 (COMPONENT_NAME,
2635 "registrationPreparer",
2636 ProxyPreparer.class,
2637 new BasicProxyPreparer());
2638 ProxyPreparer serviceLeasePreparer = config.getEntry
2639 (COMPONENT_NAME,
2640 "serviceLeasePreparer",
2641 ProxyPreparer.class,
2642 new BasicProxyPreparer());
2643 /* Task manager */
2644 ExecutorService taskMgr;
2645 try {
2646 taskMgr = config.getEntry(COMPONENT_NAME,
2647 "executorService",
2648 ExecutorService.class);
2649 } catch(NoSuchEntryException e) { /* use default */
2650 taskMgr = new ThreadPoolExecutor(
2651 MAX_N_TASKS,
2652 MAX_N_TASKS, /* Ignored */
2653 15,
2654 TimeUnit.SECONDS,
2655 new LinkedBlockingQueue(), /* Unbounded Queue */
2656 new NamedThreadFactory("JoinManager executor thread", false)
2657 );
2658 }
2659 /* Wakeup manager */
2660 WakeupManager wakeupMgr;
2661 try {
2662 wakeupMgr = config.getEntry(COMPONENT_NAME,
2663 "wakeupManager",
2664 WakeupManager.class);
2665 } catch(NoSuchEntryException e) { /* use default */
2666 wakeupMgr = new WakeupManager
2667 (new WakeupManager.ThreadDesc(null,true));
2668 }
2669 /* Max number of times to re-schedule tasks in thru wakeup manager */
2670 int maxNRetries = (config.getEntry
2671 (COMPONENT_NAME,
2672 "wakeupRetries",
2673 int.class,
2674 Integer.valueOf(6))).intValue();
2675 /* Lease renewal manager */
2676 if(leaseMgr == null) {
2677 try {
2678 leaseMgr = config.getEntry
2679 (COMPONENT_NAME,
2680 "leaseManager",
2681 LeaseRenewalManager.class);
2682 } catch(NoSuchEntryException e) { /* use default */
2683 leaseMgr = new LeaseRenewalManager(config);
2684 }
2685 }//endif
2686 long renewalDuration = (config.getEntry
2687 (COMPONENT_NAME,
2688 "maxLeaseDuration",
2689 long.class,
2690 Long.valueOf(Lease.FOREVER))).longValue();
2691 if( (renewalDuration == 0) || (renewalDuration < Lease.ANY) ) {
2692 throw new ConfigurationException("invalid configuration entry: "
2693 +"renewalDuration ("
2694 +renewalDuration+") must be "
2695 +"positive or Lease.ANY");
2696 }//endif
2697 /* Discovery manager */
2698 boolean bCreateDiscMgr = false;
2699 if(discoveryMgr == null) {
2700 bCreateDiscMgr = true;
2701 try {
2702 discoveryMgr = config.getEntry
2703 (COMPONENT_NAME,
2704 "discoveryManager",
2705 DiscoveryManagement.class);
2706 } catch(NoSuchEntryException e) { /* use default */
2707 discoveryMgr = new LookupDiscoveryManager
2708 (new String[] {""}, null, null, config);
2709 }
2710 }//endif
2711 return new Conf(registrarPreparer, registrationPreparer, serviceLeasePreparer,
2712 taskMgr, wakeupMgr, maxNRetries, leaseMgr, renewalDuration,
2713 discoveryMgr, bCreateDiscMgr);
2714 }
2715
2716 /** Convenience method invoked by the constructors of this class that
2717 * uses the given <code>Configuration</code> to initialize the current
2718 * instance of this utility, and initiates all join processing for
2719 * the given parameters. This method handles the various configurations
2720 * allowed by the different constructors.
2721 */
2722 private JoinManager(Object serviceProxy,
2723 Entry[] attrSets, ServiceID serviceID,
2724 ServiceIDListener callback, Conf conf)
2725 {
2726 registrarPreparer = conf.registrarPreparer;
2727 registrationPreparer = conf.registrationPreparer;
2728 serviceLeasePreparer = conf.serviceLeasePreparer;
2729 executor = new ExtensibleExecutorService(conf.executorService,
2730 new RunnableFutureFactory(){
2731
2732 @Override
2733 public <T> RunnableFuture<T> newTaskFor(Runnable r, T value) {
2734 if (r instanceof ProxyRegTask) return (RunnableFuture<T>) r;
2735 throw new IllegalStateException("Runnable not instance of ProxyRegTask");
2736 }
2737
2738 @Override
2739 public <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
2740 if (c instanceof ProxyRegTask) return (RunnableFuture<T>) c;
2741 throw new IllegalStateException("Callable not instance of ProxyRegTask");
2742 }
2743
2744 });
2745 proxyRegTaskQueue = new ProxyRegTaskQueue(executor);
2746 wakeupMgr = conf.wakeupManager;
2747 maxNRetries = conf.maxNretrys;
2748 leaseRenewalMgr = conf.leaseRenewalManager;
2749 renewalDuration = conf.renewalDuration;
2750 bCreateDiscMgr = conf.bcreateDisco;
2751 DiscMgrListener discMgrListen = new DiscMgrListener();
2752 if(attrSets == null) {
2753 lookupAttr = new Entry[0];
2754 } else {
2755 attrSets = attrSets.clone();
2756 LookupAttributes.check(attrSets,false);//null elements NOT ok
2757 lookupAttr = attrSets;
2758 }//endif
2759 serviceItem = new ServiceItem(serviceID, serviceProxy, lookupAttr);
2760 this.callback = callback;
2761 conf.discoveryMgr.addDiscoveryListener(discMgrListen);
2762 discMgr = conf.discoveryMgr;
2763 discMgrListener = discMgrListen;
2764 }//end createJoinManager
2765
2766 /** For the given lookup service proxy, searches the <code>joinSet</code>
2767 * for the corresponding <code>ProxyReg</code> element, and upon finding
2768 * such an element, returns that element; otherwise returns
2769 * <code>null</code>.
2770 */
2771 private ProxyReg findReg(ServiceRegistrar proxy) {
2772 for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
2773 ProxyReg reg =(ProxyReg)iter.next();
2774 if(reg.proxy.equals(proxy)) return reg;
2775 }//end loop
2776 return null;
2777 }//end findReg
2778
2779 /** Removes (from the task manager) and cancels (in the wakeup manager)
2780 * all tasks associated with the given instance of <code>ProxyReg</code>.
2781 */
2782 private void removeTasks(ProxyReg proxyReg) {
2783 if(proxyReg == null) return;
2784 if(executor == null) return;
2785 synchronized(proxyReg.taskList) {
2786 if(proxyReg.proxyRegTask != null) {
2787 proxyReg.proxyRegTask.cancel(false);
2788 proxyReg.proxyRegTask = null; //don't reuse because of seq#
2789 }//endif
2790 proxyReg.taskList.clear();
2791 }//end sync(proxyReg.taskList)
2792 proxyReg.terminate();
2793 }//end removeTasks
2794
2795 /** Removes from the task manager, all pending tasks regardless of the
2796 * the instance of <code>ProxyReg</code> with which the task is
2797 * associated, and then terminates the task manager, and makes it
2798 * a candidate for garbage collection.
2799 */
2800 private void terminateTaskMgr() {
2801 synchronized(wakeupMgr) {
2802 /* Cancel all tasks scheduled for future retry by the wakeup mgr */
2803 wakeupMgr.cancelAll();//cancel all tickets
2804 wakeupMgr.stop();//stop execution of the wakeup manager
2805 }
2806 /* Interrupt all active tasks, prepare taskMgr for GC. */
2807 executor.shutdownNow();
2808 }//end terminateTaskMgr
2809
2810 /** Examines the elements of the input set and, upon finding at least one
2811 * <code>null</code> element, throws a <code>NullPointerException</code>.
2812 */
2813 private void testForNullElement(Object[] a) {
2814 if(a == null) return;
2815 int l = a.length;
2816 for(int i=0;i<l;i++) {
2817 if(a[i] == null) {
2818 throw new NullPointerException
2819 ("input array contains at least one null element");
2820 }//endif
2821 }//end loop
2822 }//end testForNullElement
2823
2824 /** Convenience method invoked by either form of the method
2825 * <code>replaceRegistration</code>. This method registers the
2826 * given <code>serviceProxy</code> with all discovered lookup
2827 * services, replacing all current registrations. If the value
2828 * of the <code>doAttrs</code> parameter is <code>true</code>,
2829 * this method will associate the given <code>attrSets</code>
2830 * with the new service registration; otherwise, it will use
2831 * the attribute sets currently associated with the old registration.
2832 */
2833 private void replaceRegistrationDo(Object serviceProxy,
2834 Entry[] attrSets,
2835 boolean doAttrs)
2836 {
2837 if(bTerminated)
2838 throw new IllegalStateException("join manager was terminated");
2839 if(!(serviceProxy instanceof java.io.Serializable)) {
2840 throw new IllegalArgumentException
2841 ("serviceProxy must be Serializable");
2842 }//endif
2843 synchronized(this) {
2844 if(doAttrs) {
2845 if(attrSets == null) {
2846 lookupAttr = new Entry[0];
2847 } else {
2848 attrSets = (Entry[])attrSets.clone();
2849 LookupAttributes.check(attrSets,false);//no null elements
2850 lookupAttr = attrSets;
2851 }//endif
2852 }//endif
2853 serviceItem = new ServiceItem(serviceItem.serviceID, serviceProxy, lookupAttr);
2854 }
2855 Iterator<ProxyReg> it = joinSet.iterator();
2856 while (it.hasNext()){
2857 ProxyReg proxyReg = it.next();
2858 removeTasks(proxyReg);
2859 try {
2860 leaseRenewalMgr.remove( proxyReg.serviceLease );
2861 } catch (UnknownLeaseException e) { }
2862 proxyReg.addTask(new RegisterTask(proxyReg,
2863 (Entry[])lookupAttr.clone()));
2864 }//end loop
2865 }//end replaceRegistrationDo
2866
2867 }//end class JoinManager
2868