• Views
  • Iteration Report
  • My Iteration Report
  •  
OMERO.server
  • Login
  • Help/Guide
  • About Trac
  • Preferences
  • Wiki
  • Timeline
  • Roadmap
  • Browse Source
  • View Tickets
  • Search

Context Navigation

  • Last Change
  • Annotate
  • Revision Log

root/trunk/components/blitz/src/ome/services/blitz/impl/ServiceFactoryI.java

Revision 3150, 33.9 kB (checked in by ola, 7 weeks ago)

more fixes for r3148

Line 
1/*
2 *   $Id$
3 *
4 *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
5 *   Use is subject to license terms supplied in LICENSE.txt
6 */
7
8package ome.services.blitz.impl;
9
10import java.util.HashMap;
11import java.util.List;
12
13import net.sf.ehcache.Ehcache;
14import net.sf.ehcache.Element;
15import ome.api.JobHandle;
16import ome.conditions.SessionException;
17import ome.logic.HardWiredInterceptor;
18import ome.services.blitz.fire.AopContextInitializer;
19import ome.services.blitz.util.ServantHolder;
20import ome.services.blitz.util.ServiceFactoryAware;
21import ome.services.blitz.util.UnregisterServantMessage;
22import ome.services.sessions.SessionManager;
23import ome.services.util.Executor;
24import ome.system.OmeroContext;
25import ome.system.Principal;
26import ome.system.ServiceFactory;
27import omero.ApiUsageException;
28import omero.InternalException;
29import omero.ServerError;
30import omero.api.AMD_StatefulServiceInterface_close;
31import omero.api.ClientCallbackPrx;
32import omero.api.GatewayPrx;
33import omero.api.GatewayPrxHelper;
34import omero.api.IAdminPrx;
35import omero.api.IAdminPrxHelper;
36import omero.api.IConfigPrx;
37import omero.api.IConfigPrxHelper;
38import omero.api.IDeletePrx;
39import omero.api.IDeletePrxHelper;
40import omero.api.ILdapPrx;
41import omero.api.ILdapPrxHelper;
42import omero.api.IPixelsPrx;
43import omero.api.IPixelsPrxHelper;
44import omero.api.IPojosPrx;
45import omero.api.IPojosPrxHelper;
46import omero.api.IProjectionPrx;
47import omero.api.IProjectionPrxHelper;
48import omero.api.IQueryPrx;
49import omero.api.IQueryPrxHelper;
50import omero.api.IRenderingSettingsPrx;
51import omero.api.IRenderingSettingsPrxHelper;
52import omero.api.IRepositoryInfoPrx;
53import omero.api.IRepositoryInfoPrxHelper;
54import omero.api.IScriptPrx;
55import omero.api.IScriptPrxHelper;
56import omero.api.ISessionPrx;
57import omero.api.ISessionPrxHelper;
58import omero.api.ISharePrx;
59import omero.api.ISharePrxHelper;
60import omero.api.ITimelinePrx;
61import omero.api.ITimelinePrxHelper;
62import omero.api.ITypesPrx;
63import omero.api.ITypesPrxHelper;
64import omero.api.IUpdatePrx;
65import omero.api.IUpdatePrxHelper;
66import omero.api.JobHandlePrx;
67import omero.api.JobHandlePrxHelper;
68import omero.api.RawPixelsStorePrx;
69import omero.api.RawPixelsStorePrxHelper;
70import omero.api.RenderingEnginePrx;
71import omero.api.RenderingEnginePrxHelper;
72import omero.api.SearchPrx;
73import omero.api.SearchPrxHelper;
74import omero.api.ServiceInterfacePrx;
75import omero.api.ServiceInterfacePrxHelper;
76import omero.api.StatefulServiceInterfacePrx;
77import omero.api.StatefulServiceInterfacePrxHelper;
78import omero.api.ThumbnailStorePrx;
79import omero.api.ThumbnailStorePrxHelper;
80import omero.api._ServiceFactoryDisp;
81import omero.api._ServiceInterfaceOperations;
82import omero.api._StatefulServiceInterfaceOperations;
83import omero.constants.ADMINSERVICE;
84import omero.constants.CLIENTUUID;
85import omero.constants.CONFIGSERVICE;
86import omero.constants.DELETESERVICE;
87import omero.constants.GATEWAYSERVICE;
88import omero.constants.JOBHANDLE;
89import omero.constants.LDAPSERVICE;
90import omero.constants.PIXELSSERVICE;
91import omero.constants.POJOSSERVICE;
92import omero.constants.PROJECTIONSERVICE;
93import omero.constants.QUERYSERVICE;
94import omero.constants.RAWFILESTORE;
95import omero.constants.RAWPIXELSSTORE;
96import omero.constants.RENDERINGENGINE;
97import omero.constants.RENDERINGSETTINGS;
98import omero.constants.REPOSITORYINFO;
99import omero.constants.SCRIPTSERVICE;
100import omero.constants.SEARCH;
101import omero.constants.SESSIONSERVICE;
102import omero.constants.SHARESERVICE;
103import omero.constants.THUMBNAILSTORE;
104import omero.constants.TIMELINESERVICE;
105import omero.constants.TYPESSERVICE;
106import omero.constants.UPDATESERVICE;
107import omero.grid.InteractiveProcessorI;
108import omero.grid.InteractiveProcessorPrx;
109import omero.grid.InteractiveProcessorPrxHelper;
110import omero.grid.ProcessorPrx;
111import omero.grid.ProcessorPrxHelper;
112import omero.model.Job;
113import omero.model.JobStatus;
114import omero.model.JobStatusI;
115import omero.util.IceMapper;
116
117import org.aopalliance.aop.Advice;
118import org.aopalliance.intercept.MethodInterceptor;
119import org.apache.commons.logging.Log;
120import org.apache.commons.logging.LogFactory;
121import org.hibernate.Session;
122import org.springframework.aop.Advisor;
123import org.springframework.aop.framework.Advised;
124import org.springframework.beans.factory.NoSuchBeanDefinitionException;
125import org.springframework.transaction.TransactionStatus;
126
127import Ice.Current;
128
129/**
130 * Responsible for maintaining all servants for a single session.
131 *
132 * In general, try to reduce access to the {@link Ice.Current} and
133 * {@link Ice.Util} objects.
134 *
135 * @author Josh Moore, josh at glencoesoftware.com
136 * @since 3.0-Beta2
137 */
138public final class ServiceFactoryI extends _ServiceFactoryDisp {
139
140    // STATIC
141    // ===========
142
143    private final static Log log = LogFactory.getLog(ServiceFactoryI.class);
144
145    // PRIVATE STATE
146    // =================
147    // These fields are special for this instance of SF alone. It represents
148    // a single clients use of a session.
149
150    boolean doClose = true;
151
152    public final String clientId;
153
154    private ClientCallbackPrx callback;
155
156    // SHARED STATE
157    // ===================
158    // The following elements will all be the same or at least equivalent
159    // in different instances of SF attached to the same session.
160
161    final Ice.ObjectAdapter adapter;
162
163    final SessionManager sessionManager;
164
165    /**
166     * {@link Executor} to be used by servant implementations which do not
167     * delegate to the server package where all instances are wrapped with AOP
168     * for dealing with Hibernate.
169     */
170    final Executor executor;
171
172    final ServantHolder holder;
173
174    final Principal principal;
175
176    final List<HardWiredInterceptor> cptors;
177
178    final OmeroContext context;
179
180    final AopContextInitializer initializer;
181
182    // ~ Initialization and context methods
183    // =========================================================================
184
185    public ServiceFactoryI(Ice.Current current, OmeroContext context,
186            SessionManager manager, Executor executor, Principal p,
187            List<HardWiredInterceptor> interceptors) throws ApiUsageException {
188        this.adapter = current.adapter;
189        this.clientId = clientId(current);
190        this.context = context;
191        this.sessionManager = manager;
192        this.executor = executor;
193        this.principal = p;
194        this.cptors = interceptors;
195        initializer = new AopContextInitializer(
196                new ServiceFactory(this.context), this.principal);
197
198        // Setting up in memory store.
199        Ehcache cache = manager.inMemoryCache(p.getName());
200        ServantHolder local;
201        String key = "servantHolder";
202        if (!cache.isKeyInCache(key)) {
203            local = new ServantHolder();
204            cache.put(new Element(key, local));
205        } else {
206            local = (ServantHolder) cache.get(key).getObjectValue();
207        }
208        holder = local; // Set the final value
209    }
210
211    public Ice.ObjectAdapter getAdapter() {
212        return this.adapter;
213    }
214
215    public Principal getPrincipal() {
216        return this.principal;
217    }
218
219    public Executor getExecutor() {
220        return this.executor;
221    }
222
223    // ~ Stateless
224    // =========================================================================
225
226    public IAdminPrx getAdminService(Ice.Current current) throws ServerError {
227        return IAdminPrxHelper.uncheckedCast(getByName(ADMINSERVICE.value,
228                current));
229    }
230
231    public IConfigPrx getConfigService(Ice.Current current) throws ServerError {
232        return IConfigPrxHelper.uncheckedCast(getByName(CONFIGSERVICE.value,
233                current));
234    }
235
236    public IDeletePrx getDeleteService(Ice.Current current) throws ServerError {
237        return IDeletePrxHelper.uncheckedCast(getByName(DELETESERVICE.value,
238                current));
239    }
240
241    public ILdapPrx getLdapService(Ice.Current current) throws ServerError {
242        return ILdapPrxHelper.uncheckedCast(getByName(LDAPSERVICE.value,
243                current));
244    }
245
246    public IPixelsPrx getPixelsService(Ice.Current current) throws ServerError {
247        return IPixelsPrxHelper.uncheckedCast(getByName(PIXELSSERVICE.value,
248                current));
249    }
250
251    public IPojosPrx getPojosService(Ice.Current current) throws ServerError {
252        return IPojosPrxHelper.uncheckedCast(getByName(POJOSSERVICE.value,
253                current));
254    }
255
256    public IProjectionPrx getProjectionService(Ice.Current current)
257            throws ServerError {
258        return IProjectionPrxHelper.uncheckedCast(getByName(
259                PROJECTIONSERVICE.value, current));
260    }
261
262    public IQueryPrx getQueryService(Ice.Current current) throws ServerError {
263        return IQueryPrxHelper.uncheckedCast(getByName(QUERYSERVICE.value,
264                current));
265    }
266
267    public IScriptPrx getScriptService(Ice.Current current) throws ServerError {
268        Ice.ObjectPrx prx = getByName(SCRIPTSERVICE.value, current);
269        return IScriptPrxHelper.uncheckedCast(prx);
270    }
271
272    public ISessionPrx getSessionService(Current current) throws ServerError {
273        return ISessionPrxHelper.uncheckedCast(getByName(SESSIONSERVICE.value,
274                current));
275    }
276
277    public ISharePrx getShareService(Current current) throws ServerError {
278        return ISharePrxHelper.uncheckedCast(getByName(SHARESERVICE.value,
279                current));
280    }
281
282    public ITimelinePrx getTimelineService(Ice.Current current) throws ServerError {
283        return ITimelinePrxHelper.uncheckedCast(getByName(TIMELINESERVICE.value,
284                current));
285    }
286   
287    public ITypesPrx getTypesService(Ice.Current current) throws ServerError {
288        return ITypesPrxHelper.uncheckedCast(getByName(TYPESSERVICE.value,
289                current));
290    }
291
292    public IUpdatePrx getUpdateService(Ice.Current current) throws ServerError {
293        return IUpdatePrxHelper.uncheckedCast(getByName(UPDATESERVICE.value,
294                current));
295    }
296
297    public IRenderingSettingsPrx getRenderingSettingsService(Ice.Current current)
298            throws ServerError {
299        return IRenderingSettingsPrxHelper.uncheckedCast(getByName(
300                RENDERINGSETTINGS.value, current));
301    }
302
303    public IRepositoryInfoPrx getRepositoryInfoService(Ice.Current current)
304            throws ServerError {
305        return IRepositoryInfoPrxHelper.uncheckedCast(getByName(
306                REPOSITORYINFO.value, current));
307    }
308
309    // ~ Stateful
310    // =========================================================================
311
312    public GatewayPrx createGateway(Ice.Current current) throws ServerError {
313        return GatewayPrxHelper.uncheckedCast(createByName(
314                GATEWAYSERVICE.value, current));
315    }
316
317    public JobHandlePrx createJobHandle(Ice.Current current) throws ServerError {
318        return JobHandlePrxHelper.uncheckedCast(createByName(JOBHANDLE.value,
319                current));
320    }
321
322    public RenderingEnginePrx createRenderingEngine(Ice.Current current)
323            throws ServerError {
324        return RenderingEnginePrxHelper.uncheckedCast(createByName(
325                RENDERINGENGINE.value, current));
326    }
327
328    public omero.api.RawFileStorePrx createRawFileStore(Ice.Current current)
329            throws ServerError {
330        return omero.api.RawFileStorePrxHelper.uncheckedCast(createByName(
331                RAWFILESTORE.value, current));
332    }
333
334    public RawPixelsStorePrx createRawPixelsStore(Ice.Current current)
335            throws ServerError {
336        return RawPixelsStorePrxHelper.uncheckedCast(createByName(
337                RAWPIXELSSTORE.value, current));
338    }
339
340    public SearchPrx createSearchService(Ice.Current current)
341            throws ServerError {
342        return SearchPrxHelper
343                .uncheckedCast(createByName(SEARCH.value, current));
344    }
345
346    public ThumbnailStorePrx createThumbnailStore(Ice.Current current)
347            throws ServerError {
348        return ThumbnailStorePrxHelper.uncheckedCast(createByName(
349                THUMBNAILSTORE.value, current));
350    }
351
352    // ~ Other interface methods
353    // =========================================================================
354
355    public ServiceInterfacePrx getByName(String name, Current current)
356            throws ServerError {
357
358        Ice.Identity id = getIdentity(name);
359
360        holder.acquireLock(name);
361        try {
362            Ice.ObjectPrx prx;
363            Ice.Object servant = holder.get(name);
364            if (servant == null) {
365                servant = createServantDelegate(name);
366                // Previously we checked for stateful services here,
367                // however the logic is the same so it shouldn't
368                // cause any issues.
369                prx = registerServant(current, id, servant);
370            } else {
371                prx = adapter.createProxy(id);
372            }
373            return ServiceInterfacePrxHelper.uncheckedCast(prx);
374        } finally {
375            holder.releaseLock(name);
376        }
377    }
378
379    public StatefulServiceInterfacePrx createByName(String name, Current current)
380            throws ServerError {
381
382        Ice.Identity id = getIdentity(Ice.Util.generateUUID() + name);
383        if (null != adapter.find(id)) {
384            omero.InternalException ie = new omero.InternalException();
385            ie.message = name + " already registered for this adapter.";
386        }
387
388        Ice.Object servant = createServantDelegate(name);
389        Ice.ObjectPrx prx = registerServant(current, id, servant);
390        return StatefulServiceInterfacePrxHelper.uncheckedCast(prx);
391    }
392
393    public InteractiveProcessorPrx acquireProcessor(final Job submittedJob,
394            int seconds, Current current) throws ServerError {
395
396        if (seconds > (3 * 60)) {
397            ApiUsageException aue = new ApiUsageException();
398            aue.message = "Delay is too long. Maximum = 3 minutes.";
399        }
400
401        final IceMapper mapper = new IceMapper();
402
403        // First create the job with a status of WAITING.
404        // The InteractiveProcessor will be responsible for its
405        // further lifetime.
406        final ome.model.jobs.Job savedJob = (ome.model.jobs.Job) this.executor
407                .execute(this.principal, new Executor.Work() {
408
409                    public ome.model.jobs.Job doWork(
410                            TransactionStatus txStatus, Session session,
411                            ServiceFactory sf) {
412
413                        final JobHandle handle = sf.createJobHandle();
414                        try {
415                            JobStatus status = new JobStatusI();
416                            status.setValue(omero.rtypes
417                                    .rstring(JobHandle.WAITING));
418                            submittedJob.setStatus(status);
419                            submittedJob.setMessage(omero.rtypes
420                                    .rstring("Interactive job. Waiting."));
421
422                            handle.submit((ome.model.jobs.Job) mapper
423                                    .reverse(submittedJob));
424                            return handle.getJob();
425                        } catch (ApiUsageException e) {
426                            return null;
427                        } finally {
428                            if (handle != null) {
429                                handle.close();
430                            }
431                        }
432                    }
433                });
434
435        if (savedJob == null) {
436            throw new ApiUsageException(null, null, "Could not submit job. ");
437        }
438
439        // Unloading job to prevent lazy-initialization exceptions.
440        Job unloadedJob = (Job) mapper.map(savedJob);
441        unloadedJob.unload();
442
443        // Lookup processor
444        // Create wrapper (InteractiveProcessor)
445        // Create session (with session)
446        // Setup environment
447        // Send off to processor
448        long start = System.currentTimeMillis();
449        long stop = seconds < 0 ? start : (start + (seconds * 1000L));
450        do {
451
452            Ice.ObjectPrx objectPrx = adapter.getCommunicator().stringToProxy(
453                    "IceGrid/Query");
454            IceGrid.QueryPrx query = IceGrid.QueryPrxHelper
455                    .checkedCast(objectPrx);
456            Ice.ObjectPrx[] candidates = query
457                    .findAllObjectsByType("::omero::grid::Processor");
458
459            // //current.con
460
461            for (Ice.ObjectPrx op : candidates) {
462                ProcessorPrx p;
463                try {
464                    p = ProcessorPrxHelper.checkedCast(op);
465                    if (p != null) {
466                        // p.login()
467                    }
468                } catch (Exception e) {
469                    // continue
470                }
471            }
472
473            Ice.ObjectPrx prx = adapter.getCommunicator().stringToProxy(
474                    "Processor");
475            if (prx != null) {
476                ProcessorPrx processor;
477                try {
478                    processor = ProcessorPrxHelper.checkedCast(prx);
479                    if (processor != null) {
480                        long timeout = System.currentTimeMillis() + 60 * 60 * 1000L;
481                        InteractiveProcessorI ip = new InteractiveProcessorI(
482                                this.principal, this.sessionManager,
483                                this.executor, processor, unloadedJob, timeout);
484                        Ice.Identity id = new Ice.Identity();
485                        id.category = current.id.name;
486                        id.name = Ice.Util.generateUUID();
487                        Ice.ObjectPrx rv = registerServant(current, id, ip);
488                        return InteractiveProcessorPrxHelper.uncheckedCast(rv);
489                    }
490                    try {
491                        Thread.sleep((stop - start) / 10);
492                    } catch (InterruptedException ie) {
493                        // ok.
494                    }
495                } catch (Ice.NoEndpointException nee) {
496                    // This means that there probably is none.
497                    // Wait a little longer
498                    try {
499                        Thread.sleep((stop - start) / 3);
500                    } catch (InterruptedException ie) {
501                        // ok.
502                    }
503                }
504            }
505        } while (stop < System.currentTimeMillis());
506        return null;
507    }
508
509    public void setCallback(ClientCallbackPrx callback, Ice.Current current) {
510        this.callback = callback;
511    }
512
513    public void detachOnDestroy(Ice.Current current) {
514        doClose = false;
515    }
516
517    @Deprecated
518    public void close(Ice.Current current) {
519        doClose = false;
520    }
521
522    public void closeOnDestroy(Ice.Current current) {
523        doClose = true;
524    }
525
526    /**
527     * Destruction simply decrements the reference count for a session to allow
528     * reconnecting to it. This means that the Glacier timeout property is
529     * fairly unimportant. If a Glacier connection times out or is otherwise
530     * destroyed, a client can attempt to reconnect.
531     *
532     * However, in the case of only one reference to the session, if the
533     * Glacier2 timeout is greater than the session timeout, exceptions can be
534     * thrown when this method tries to clean up the session. Therefore all
535     * session access must be guarded by a try/finally block.
536     */
537    public void destroy(Ice.Current current) {
538
539        int ref;
540        try {
541            // First detach and get the reference count.
542            ref = sessionManager.detach(this.principal.getName());
543        } catch (SessionException rse) {
544            // If the session has already been removed or has timed out,
545            // then we should do everything we can to clean up.
546            log.info("Session already removed. Cleaning up blitz state.");
547            ref = 0;
548            doClose = true;
549        }
550
551        // If we are supposed to close, do only so if the ref count
552        // is < 1.
553        if (doClose && ref < 1) {
554
555            // Must check all session access in this method too.
556            doDestroy();
557
558            try {
559                ref = sessionManager.close(this.principal.getName());
560            } catch (SessionException se) {
561                // An exception could still theoretically be thrown here
562                // if the timeout/removal happened since the last call.
563                // Therefore, we'll just let another exception be thrown
564                // since the time for shutdown is not overly critical.
565            }
566
567        }
568
569        // All resources cleaned up or not based on the reference count.
570        // Now we can remove the current session. If an exception if thrown,
571        // there's not much we can do.
572        try {
573            adapter.remove(sessionId());
574        } catch (Ice.ObjectAdapterDeactivatedException oade) {
575            log.warn("Adapter already deactivated. Cannot remove: "+sessionId());
576        } catch (Throwable t) {
577            // FIXME
578            log.error("Possible memory leak: can't remove service factory", t);
579        }
580
581    }
582
583    /**
584     * Performs the actual cleanup operation on all the resources shared between
585     * this and other {@link ServiceFactoryI} instances in the same
586     * {@link Session}. Since {@link #destroy()} is called regardless by the
587     * router, even when a client has just died, we have this internal method
588     * for handling the actual closing of resources.
589     *
590     * This method must take precautions to not throw a {@link SessionException}
591     * . See {@link #destroy(Current)} for more information.
592     */
593    public void doDestroy() {
594
595        if (log.isInfoEnabled()) {
596            log.info(String.format("Closing %s session", this));
597        }
598
599        // Cleaning up resources
600        // =================================================
601        holder.acquireLock("*"); // Protects all the servants on destruction
602        try {
603            List<String> servants = holder.getServantList();
604            for (final String key : servants) {
605                final Ice.Object servantOrTie = holder.get(key);
606                final Ice.Identity id = getIdentity(key);
607
608                if (servantOrTie == null) {
609                    log.warn("Servant already removed: " + key);
610                    // But calling unregister just in case
611                    unregisterServant(id);
612                    continue; // LOOP.
613                }
614
615                // All errors are ignored within the loop.
616                try {
617                    Object servant;
618                    if (servantOrTie instanceof Ice.TieBase) {
619                        servant = ((Ice.TieBase) servantOrTie).ice_delegate();
620                    } else {
621                        servant = servantOrTie;
622                    }
623
624                    // Now that we have the servant instance, we do what we can
625                    // to clean it up. Stateful services must use the callback
626                    // mechanism of IceMethodInvoker. InteractiveProcessors must
627                    // be stopped and unregistered. Stateless must only be
628                    // unregistered.
629                    //
630                    // TODO: put all of this in the AbstractAmdServant class.
631                    if (servant instanceof _StatefulServiceInterfaceOperations) {
632
633                        // Cleanup stateful
634                        // ----------------
635                        // Here we call the "close()" method on all methods
636                        // which
637                        // require that logic, allowing the IceMethodInvoker to
638                        // raise the UnregisterServantEvent, otherwise there is
639                        // a
640                        // recursive call back to close
641                        final _StatefulServiceInterfaceOperations stateful = (_StatefulServiceInterfaceOperations) servant;
642                        final Ice.Current __curr = new Ice.Current();
643                        __curr.id = id;
644                        __curr.adapter = adapter;
645                        __curr.operation = "close";
646                        __curr.ctx = new HashMap<String, String>();
647                        __curr.ctx.put(CLIENTUUID.value, clientId);
648                        // We have to be more intelligent about this. The call
649                        // should really happen in the same thread so that it's
650                        // complete before the service factory is removed.
651                        stateful.close_async(
652                                new AMD_StatefulServiceInterface_close() {
653                                    public void ice_exception(Exception ex) {
654                                        log.error("Error on close callback: "
655                                                + key + "=" + stateful);
656                                    }
657
658                                    public void ice_response() {
659                                        // Ok.
660                                    }
661
662                                }, __curr);
663
664                    } else {
665                        if (servant instanceof InteractiveProcessorI) {
666                            // Cleanup interactive processors
667                            // ------------------------------
668                            InteractiveProcessorI ip = (InteractiveProcessorI) servant;
669                            ip.stop();
670                        } else if (servant instanceof _ServiceInterfaceOperations) {
671                            // Cleanup stateless
672                            // -----------------
673                            // Do nothing.
674                        } else {
675                            throw new ome.conditions.InternalException(
676                                    "Unknown servant type: " + servant);
677                        }
678                    }
679                } catch (Exception e) {
680                    log.error("Error destroying servant: " + key + "="
681                            + servantOrTie, e);
682                } finally {
683                    // Now we will again try to remove the servant, which may
684                    // have already been done, after the method call, though, it
685                    // is guaranteed to no longer be active.
686                    unregisterServant(id);
687                    log.info("Removed servant from adapter: " + key);
688                }
689            }
690        } finally {
691            holder.releaseLock("*");
692        }
693    }
694
695    public List