/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.jini.jeri.internal.runtime; import java.io.IOException; import java.io.InterruptedIOException; import java.rmi.Remote; import java.rmi.server.ExportException; import java.rmi.server.Unreferenced; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import net.jini.id.Uuid; import net.jini.jeri.Endpoint; import net.jini.jeri.InvocationDispatcher; import net.jini.jeri.RequestDispatcher; import net.jini.jeri.ServerEndpoint; import net.jini.jeri.ServerEndpoint.ListenContext; import net.jini.jeri.ServerEndpoint.ListenCookie; import net.jini.jeri.ServerEndpoint.ListenEndpoint; import net.jini.jeri.ServerEndpoint.ListenHandle; import net.jini.security.Security; /** * An ObjectTable front end for exporting remote objects with a * ServerEndpoint as the producer of InboundRequests. * * A BasicExportTable manages a pool of ServerEndpoint.ListenEndpoints * being listened on. * * @author Sun Microsystems, Inc. **/ public final class BasicExportTable { /** * listen pool marker value to signal that a listen operation on a * ListenEndpoint is currently being started by another thread **/ private static final Object PENDING = new Object(); /** underlying object table */ private final ObjectTable objectTable = new ObjectTable(); /** guards listenPool and all Binding.exportsInProgress fields */ private final Object lock = new Object(); /** * pool of endpoints that we're listening on: * maps SameClassKey(ServerEndpoint.ListenEndpoint) to Binding **/ private final Map listenPool = new HashMap(); /** * Creates a new instance. **/ public BasicExportTable() { } /** * Exports a remote object to this BasicExportTable. **/ public Entry export(Remote impl, ServerEndpoint serverEndpoint, boolean allowDGC, boolean keepAlive, Uuid id) throws ExportException { List bindings = null; ObjectTable.Target target = null; Endpoint endpoint; try { LC listenContext = new LC(); try { endpoint = serverEndpoint.enumerateListenEndpoints(listenContext); } catch (IOException e) { throw new ExportException("listen failed", e); } finally { bindings = listenContext.getFinalBindings(); } RequestDispatcher[] requestDispatchers = new RequestDispatcher[bindings.size()]; for (int i = 0; i < requestDispatchers.length; i++) { requestDispatchers[i] = ((Binding) bindings.get(i)).requestDispatcher; } target = objectTable.export( impl, requestDispatchers, allowDGC, keepAlive, id); } finally { if (bindings != null) { /* * All bindings in the listen context have had their * exportsInProgress fields incremented, so they must * all be decremented here. */ for (int i = 0; i < bindings.size(); i++) { Binding binding = (Binding) bindings.get(i); synchronized (lock) { binding.exportsInProgress--; } /* * If export wasn't successful, check to see if * binding can be released. */ if (target == null) { binding.checkReferenced(); } } } } return new Entry(bindings, target, endpoint); } /** * Represents a remote object exported to this BasicExportTable. * * An Entry can be used to get the client-side Endpoint to use to * communicate with the exported object, to set the invocation * dispatcher for the exported object, and to unexport the * exported object. **/ public static final class Entry { private final List bindings; private final ObjectTable.Target target; private final Endpoint endpoint; Entry(List bindings, ObjectTable.Target target, Endpoint endpoint) { this.bindings = bindings; this.target = target; this.endpoint = endpoint; } /** * Returns the client-side Endpoint to use to communicate * with the exported object. **/ public Endpoint getEndpoint() { return endpoint; } /** * Sets the invocation dispatcher for the exported object. **/ public void setInvocationDispatcher(InvocationDispatcher id) { target.setInvocationDispatcher(id); } /** * Unexports the exported object. **/ public boolean unexport(boolean force) { if (!target.unexport(force)) { return false; } for (int i = 0; i < bindings.size(); i++) { ((Binding) bindings.get(i)).checkReferenced(); } return true; } } /** * Returns the binding for the specified ListenEndpoint, by * returning the one already in the listen pool, if any, or else * by creating a new one. If a Binding is returned, its * exportsInProgress field will have been incremented. **/ private Binding getBinding(ListenEndpoint listenEndpoint) throws IOException { Object key = new SameClassKey(listenEndpoint); Binding binding = null; synchronized (lock) { do { Object value = listenPool.get(key); if (value instanceof Binding) { binding = (Binding) value; binding.exportsInProgress++; return binding; } else if (value == PENDING) { try { lock.wait(); } catch (InterruptedException e) { throw new InterruptedIOException(); } continue; } else { assert value == null; listenPool.put(key, PENDING); break; } } while (true); } try { // start listen operation without holding global lock binding = new Binding(listenEndpoint); } finally { synchronized (lock) { assert listenPool.get(key) == PENDING; if (binding != null) { listenPool.put(key, binding); binding.exportsInProgress++; } else { listenPool.remove(key); } lock.notifyAll(); } } return binding; } /** * Collects the ListenEndpoints associated with a ServerEndpoint * and gets the corresponding bindings using the listen pool. **/ private class LC implements ServerEndpoint.ListenContext { private boolean done = false; private final List bindings = new ArrayList(); LC() { } public synchronized ListenCookie addListenEndpoint( ListenEndpoint listenEndpoint) throws IOException { if (done) { throw new IllegalStateException(); } // must always check permissions before exposing pool listenEndpoint.checkPermissions(); Binding binding = getBinding(listenEndpoint); bindings.add(binding); return binding.listenHandle.getCookie(); } synchronized List getFinalBindings() { done = true; return bindings; } } /** * A bound ListenEndpoint and the associated ListenHandle and * RequestDispatcher. **/ private class Binding { private final ListenEndpoint listenEndpoint; final RequestDispatcher requestDispatcher; final ListenHandle listenHandle; int exportsInProgress = 0; // guarded by outer "lock" /** * Creates a binding for the specified ListenEndpoint by * attempting to listen on it. **/ Binding(final ListenEndpoint listenEndpoint) throws IOException { this.listenEndpoint = listenEndpoint; requestDispatcher = objectTable.createRequestDispatcher(new Unreferenced() { public void unreferenced() { checkReferenced(); } }); try { /* * We don't want this (potentially) shared listen * operation to inherit the access control context of * the current callers arbitrarily (their permissions * were already checked by the ListenContext, and the * ObjectTable will take care of checking permissions * per requests against the appropriate callers' * access control context). */ listenHandle = (ListenHandle) Security.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws IOException { return listenEndpoint.listen(requestDispatcher); } }); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } /** * Checks whether there are any objects currently exported to * this binding's RequestDispatcher or if there are any * exports in progress for this binding; if there are neither, * this binding is removed from the listen pool and its listen * operation is closed. **/ void checkReferenced() { synchronized (lock) { if (exportsInProgress > 0 || objectTable.isReferenced(requestDispatcher)) { return; } listenPool.remove(new SameClassKey(listenEndpoint)); } listenHandle.close(); } } }