aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
blob: 950ada2c299e64cbb3771af4e366fd116ba3cb6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/*
 * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.rmi.server;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.rmi.MarshalledObject;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.rmi.activation.ActivationID;
import java.rmi.activation.UnknownObjectException;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import sun.rmi.registry.RegistryImpl;

/**
 * The default activation group implementation.
 *
 * @author      Ann Wollrath
 * @since       1.2
 * @see         java.rmi.activation.ActivationGroup
 */
public class ActivationGroupImpl extends ActivationGroup {

    // use serialVersionUID from JDK 1.2.2 for interoperability
    private static final long serialVersionUID = 5758693559430427303L;

    /** maps persistent IDs to activated remote objects */
    private final Hashtable<ActivationID,ActiveEntry> active =
        new Hashtable<ActivationID,ActiveEntry>();
    private boolean groupInactive = false;
    private final ActivationGroupID groupID;
    private final List<ActivationID> lockedIDs = new ArrayList<ActivationID>();

    /**
     * Creates a default activation group implementation.
     *
     * @param id the group's identifier
     * @param data ignored
     */
    public ActivationGroupImpl(ActivationGroupID id, MarshalledObject<?> data)
        throws RemoteException
    {
        super(id);
        groupID = id;

        /*
         * Unexport activation group impl and attempt to export it on
         * an unshared anonymous port.  See 4692286.
         */
        unexportObject(this, true);
        RMIServerSocketFactory ssf = new ServerSocketFactoryImpl();
        UnicastRemoteObject.exportObject(this, 0, null, ssf);

        if (System.getSecurityManager() == null) {
            try {
                // Provide a default security manager.
                System.setSecurityManager(new SecurityManager());

            } catch (Exception e) {
                throw new RemoteException("unable to set security manager", e);
            }
        }
    }

    /**
     * Trivial server socket factory used to export the activation group
     * impl on an unshared port.
     */
    private static class ServerSocketFactoryImpl
        implements RMIServerSocketFactory
    {
        public ServerSocket createServerSocket(int port) throws IOException
        {
            RMISocketFactory sf = RMISocketFactory.getSocketFactory();
            if (sf == null) {
                sf = RMISocketFactory.getDefaultSocketFactory();
            }
            return sf.createServerSocket(port);
        }
    }

    /*
     * Obtains a lock on the ActivationID id before returning. Allows only one
     * thread at a time to hold a lock on a particular id.  If the lock for id
     * is in use, all requests for an equivalent (in the Object.equals sense)
     * id will wait for the id to be notified and use the supplied id as the
     * next lock. The caller of "acquireLock" must execute the "releaseLock"
     * method" to release the lock and "notifyAll" waiters for the id lock
     * obtained from this method.  The typical usage pattern is as follows:
     *
     * try {
     *    acquireLock(id);
     *    // do stuff pertaining to id...
     * } finally {
     *    releaseLock(id);
     *    checkInactiveGroup();
     * }
     */
    private void acquireLock(ActivationID id) {

        ActivationID waitForID;

        for (;;) {

            synchronized (lockedIDs) {
                int index = lockedIDs.indexOf(id);
                if (index < 0) {
                    lockedIDs.add(id);
                    return;
                } else {
                    waitForID = lockedIDs.get(index);
                }
            }

            synchronized (waitForID) {
                synchronized (lockedIDs) {
                    int index = lockedIDs.indexOf(waitForID);
                    if (index < 0) continue;
                    ActivationID actualID = lockedIDs.get(index);
                    if (actualID != waitForID)
                        /*
                         * don't wait on an id that won't be notified.
                         */
                        continue;
                }

                try {
                    waitForID.wait();
                } catch (InterruptedException ignore) {
                }
            }
        }

    }

    /*
     * Releases the id lock obtained via the "acquireLock" method and then
     * notifies all threads waiting on the lock.
     */
    private void releaseLock(ActivationID id) {
        synchronized (lockedIDs) {
            id = lockedIDs.remove(lockedIDs.indexOf(id));
        }

        synchronized (id) {
            id.notifyAll();
        }
    }

    /**
     * Creates a new instance of an activatable remote object. The
     * <code>Activator</code> calls this method to create an activatable
     * object in this group. This method should be idempotent; a call to
     * activate an already active object should return the previously
     * activated object.
     *
     * Note: this method assumes that the Activator will only invoke
     * newInstance for the same object in a serial fashion (i.e.,
     * the activator will not allow the group to see concurrent requests
     * to activate the same object.
     *
     * @param id the object's activation identifier
     * @param desc the object's activation descriptor
     * @return a marshalled object containing the activated object's stub
     */
    public MarshalledObject<? extends Remote>
                                      newInstance(final ActivationID id,
                                                  final ActivationDesc desc)
        throws ActivationException, RemoteException
    {
        RegistryImpl.checkAccess("ActivationInstantiator.newInstance");

        if (!groupID.equals(desc.getGroupID()))
            throw new ActivationException("newInstance in wrong group");

        try {
            acquireLock(id);
            synchronized (this) {
                if (groupInactive == true)
                    throw new InactiveGroupException("group is inactive");
            }

            ActiveEntry entry = active.get(id);
            if (entry != null)
                return entry.mobj;

            String className = desc.getClassName();

            final Class<? extends Remote> cl =
                RMIClassLoader.loadClass(desc.getLocation(), className)
                .asSubclass(Remote.class);
            Remote impl = null;

            final Thread t = Thread.currentThread();
            final ClassLoader savedCcl = t.getContextClassLoader();
            ClassLoader objcl = cl.getClassLoader();
            final ClassLoader ccl = covers(objcl, savedCcl) ? objcl : savedCcl;

            /*
             * Fix for 4164971: allow non-public activatable class
             * and/or constructor, create the activatable object in a
             * privileged block
             */
            try {
                /*
                 * The code below is in a doPrivileged block to
                 * protect against user code which code might have set
                 * a global socket factory (in which case application
                 * code would be on the stack).
                 */
                impl = AccessController.doPrivileged(
                      new PrivilegedExceptionAction<Remote>() {
                      public Remote run() throws InstantiationException,
                          NoSuchMethodException, IllegalAccessException,
                          InvocationTargetException
                      {
                          Constructor<? extends Remote> constructor =
                              cl.getDeclaredConstructor(
                                  ActivationID.class, MarshalledObject.class);
                          constructor.setAccessible(true);
                          try {
                              /*
                               * Fix for 4289544: make sure to set the
                               * context class loader to be the class
                               * loader of the impl class before
                               * constructing that class.
                               */
                              t.setContextClassLoader(ccl);
                              return constructor.newInstance(id,
                                                             desc.getData());
                          } finally {
                              t.setContextClassLoader(savedCcl);
                          }
                      }
                  });
            } catch (PrivilegedActionException pae) {
                Throwable e = pae.getException();

                // narrow the exception's type and rethrow it
                if (e instanceof InstantiationException) {
                    throw (InstantiationException) e;
                } else if (e instanceof NoSuchMethodException) {
                    throw (NoSuchMethodException) e;
                } else if (e instanceof IllegalAccessException) {
                    throw (IllegalAccessException) e;
                } else if (e instanceof InvocationTargetException) {
                    throw (InvocationTargetException) e;
                } else if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else if (e instanceof Error) {
                    throw (Error) e;
                }
            }

            entry = new ActiveEntry(impl);
            active.put(id, entry);
            return entry.mobj;

        } catch (NoSuchMethodException e) {
            /* user forgot to provide activatable constructor? */
            throw new ActivationException
                ("Activatable object must provide an activation"+
                 " constructor", e);

        } catch (NoSuchMethodError e) {
            /* code recompiled and user forgot to provide
             *  activatable constructor?
             */
            throw new ActivationException
                ("Activatable object must provide an activation"+
                 " constructor", e );

        } catch (InvocationTargetException e) {
            throw new ActivationException("exception in object constructor",
                                          e.getTargetException());

        } catch (Exception e) {
            throw new ActivationException("unable to activate object", e);
        } finally {
            releaseLock(id);
            checkInactiveGroup();
        }
    }


   /**
    * The group's <code>inactiveObject</code> method is called
    * indirectly via a call to the <code>Activatable.inactive</code>
    * method. A remote object implementation must call
    * <code>Activatable</code>'s <code>inactive</code> method when
    * that object deactivates (the object deems that it is no longer
    * active). If the object does not call
    * <code>Activatable.inactive</code> when it deactivates, the
    * object will never be garbage collected since the group keeps
    * strong references to the objects it creates. <p>
    *
    * The group's <code>inactiveObject</code> method
    * unexports the remote object from the RMI runtime so that the
    * object can no longer receive incoming RMI calls. This call will
    * only succeed if the object has no pending/executing calls. If
    * the object does have pending/executing RMI calls, then false
    * will be returned.
    *
    * If the object has no pending/executing calls, the object is
    * removed from the RMI runtime and the group informs its
    * <code>ActivationMonitor</code> (via the monitor's
    * <code>inactiveObject</code> method) that the remote object is
    * not currently active so that the remote object will be
    * re-activated by the activator upon a subsequent activation
    * request.
    *
    * @param id the object's activation identifier
    * @returns true if the operation succeeds (the operation will
    * succeed if the object in currently known to be active and is
    * either already unexported or is currently exported and has no
    * pending/executing calls); false is returned if the object has
    * pending/executing calls in which case it cannot be deactivated
    * @exception UnknownObjectException if object is unknown (may already
    * be inactive)
    * @exception RemoteException if call informing monitor fails
    */
    public boolean inactiveObject(ActivationID id)
        throws ActivationException, UnknownObjectException, RemoteException
    {

        try {
            acquireLock(id);
            synchronized (this) {
                if (groupInactive == true)
                    throw new ActivationException("group is inactive");
            }

            ActiveEntry entry = active.get(id);
            if (entry == null) {
                // REMIND: should this be silent?
                throw new UnknownObjectException("object not active");
            }

            try {
                if (Activatable.unexportObject(entry.impl, false) == false)
                    return false;
            } catch (NoSuchObjectException allowUnexportedObjects) {
            }

            try {
                super.inactiveObject(id);
            } catch (UnknownObjectException allowUnregisteredObjects) {
            }

            active.remove(id);

        } finally {
            releaseLock(id);
            checkInactiveGroup();
        }

        return true;
    }

    /*
     * Determines if the group has become inactive and
     * marks it as such.
     */
    private void checkInactiveGroup() {
        boolean groupMarkedInactive = false;
        synchronized (this) {
            if (active.size() == 0 && lockedIDs.size() == 0 &&
                groupInactive == false)
            {
                groupInactive = true;
                groupMarkedInactive = true;
            }
        }

        if (groupMarkedInactive) {
            try {
                super.inactiveGroup();
            } catch (Exception ignoreDeactivateFailure) {
            }

            try {
                UnicastRemoteObject.unexportObject(this, true);
            } catch (NoSuchObjectException allowUnexportedGroup) {
            }
        }
    }

    /**
     * The group's <code>activeObject</code> method is called when an
     * object is exported (either by <code>Activatable</code> object
     * construction or an explicit call to
     * <code>Activatable.exportObject</code>. The group must inform its
     * <code>ActivationMonitor</code> that the object is active (via
     * the monitor's <code>activeObject</code> method) if the group
     * hasn't already done so.
     *
     * @param id the object's identifier
     * @param obj the remote object implementation
     * @exception UnknownObjectException if object is not registered
     * @exception RemoteException if call informing monitor fails
     */
    public void activeObject(ActivationID id, Remote impl)
        throws ActivationException, UnknownObjectException, RemoteException
    {

        try {
            acquireLock(id);
            synchronized (this) {
                if (groupInactive == true)
                    throw new ActivationException("group is inactive");
            }
            if (!active.contains(id)) {
                ActiveEntry entry = new ActiveEntry(impl);
                active.put(id, entry);
                // created new entry, so inform monitor of active object
                try {
                    super.activeObject(id, entry.mobj);
                } catch (RemoteException e) {
                    // daemon can still find it by calling newInstance
                }
            }
        } finally {
            releaseLock(id);
            checkInactiveGroup();
        }
    }

    /**
     * Entry in table for active object.
     */
    private static class ActiveEntry {
        Remote impl;
        MarshalledObject<Remote> mobj;

        ActiveEntry(Remote impl) throws ActivationException {
            this.impl =  impl;
            try {
                this.mobj = new MarshalledObject<Remote>(impl);
            } catch (IOException e) {
                throw new
                    ActivationException("failed to marshal remote object", e);
            }
        }
    }

    /**
     * Returns true if the first argument is either equal to, or is a
     * descendant of, the second argument.  Null is treated as the root of
     * the tree.
     */
    private static boolean covers(ClassLoader sub, ClassLoader sup) {
        if (sup == null) {
            return true;
        } else if (sub == null) {
            return false;
        }
        do {
            if (sub == sup) {
                return true;
            }
            sub = sub.getParent();
        } while (sub != null);
        return false;
    }
}