/*
* 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.rmi.Remote;
import java.rmi.server.ExportException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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.ListenCookie;
import net.jini.jeri.ServerEndpoint.ListenEndpoint;
/**
* 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 {
/** underlying object table */
private final ObjectTable objectTable = new ObjectTable();
/**
* pool of endpoints that we're listening on:
* maps SameClassKey(ServerEndpoint.ListenEndpoint) to Binding.
* A binding removes itself from the listen pool.
**/
private final ConcurrentMap<SameClassKey,Binding> listenPool =
new ConcurrentHashMap<SameClassKey,Binding>(128);// 128 to reduce map resizing
/**
* 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;
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)).getRequestDispatcher();
}
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);
binding.decrementExportInProgress();
/*
* 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 Target target;
private final Endpoint endpoint;
Entry(List bindings, 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
{
SameClassKey key = new SameClassKey(listenEndpoint);
Binding binding = null;
// This while loop ensures that a binding has it's exportInProgress
// field incremented and the binding was not closed prior.
// Once the exportInProgress field is incremented, the binding will stay active.
// It is still possible for activation to be unsuccessful, resulting
// in an IOException.
// The reason for this while loop, is Binding's remove themselves from
// the listenPool if inactive, a binding may be removed from the
// listenPool by another thread without the current threads knowledge.
// This will only happen while the binding has no Exports in progress.
// Thus the increment calls are checked to be active;
while (binding == null){
binding = listenPool.get(key);
if ( binding == null){
binding = new Binding(listenEndpoint,objectTable, listenPool);
Binding existed = listenPool.putIfAbsent(key, binding);
if (existed != null){
binding = existed;
boolean active = binding.incrementExportInProgress();
if (!active){
binding = null;
}
continue;
} else {
boolean active = binding.activate();
if (!active){
binding = null;
}
continue;
}
} else {
// Although unlikely the binding could become inactive
// after retrieval, since the operation of getting and checking is not atomic.
// If inactive, the binding has removed itself from the listenPool.
boolean active = binding.incrementExportInProgress();
if (!active) {
binding = null;
// This binding will have removed itself from listenPool.
}
}
}
binding.activate(); //Prevent a thread returning normally with an inactive object
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 volatile boolean done = false;
private final List<Binding> bindings =
Collections.synchronizedList(new ArrayList<Binding>());
LC() { }
public 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.getListenHandle().getCookie();
}
private List getFinalBindings() {
done = true;
return bindings;
}
}
}