/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
public abstract class ClientNotifForwarder {
}
private static int threadId;
/* An Executor that allows at most one executing and one pending
Runnable. It uses at most one thread -- as soon as there is
no pending Runnable the thread can exit. Another thread is
created as soon as there is a new pending Runnable. This
Executor is adapted for use in a situation where each Runnable
usually schedules up another Runnable. On return from the
first one, the second one is immediately executed. So this
just becomes a complicated way to write a while loop, but with
the advantage that you can replace it with another Executor,
for instance one that you are using to execute a bunch of other
unrelated work.
You might expect that a java.util.concurrent.ThreadPoolExecutor
with corePoolSize=0 and maximumPoolSize=1 would have the same
behavior, but it does not. A ThreadPoolExecutor only creates
a new thread when a new task is submitted and the number of
existing threads is < corePoolSize. This can never happen when
corePoolSize=0, so new threads are never created. Surprising,
but there you are.
*/
throw new IllegalArgumentException("More than one command");
public void run() {
while (true) {
Runnable r;
synchronized (LinearExecutor.this) {
return;
} else {
r = LinearExecutor.this.command;
}
}
r.run();
}
}
};
}
}
}
/* You can supply an Executor in which the remote call to
fetchNotifications will be made. The Executor's execute
method reschedules another task, so you must not use
an Executor that executes tasks in the caller's thread. */
ex = new LinearExecutor();
this.defaultClassLoader = defaultClassLoader;
}
/**
* Called to to fetch notifications from a server.
*/
int maxNotifications,
long timeout)
throws IOException, ClassNotFoundException;
throws IOException, InstanceNotFoundException;
throws IOException, InstanceNotFoundException,
/**
* Used to send out a notification about lost notifs
*/
throws IOException, InstanceNotFoundException {
}
name,
init(false);
}
public synchronized Integer[]
throws ListenerNotFoundException, IOException {
beforeRemove();
}
}
}
throw new ListenerNotFoundException("Listener not found");
}
public synchronized Integer
throws ListenerNotFoundException, IOException {
}
beforeRemove();
break;
}
}
throw new ListenerNotFoundException("Listener not found");
return id;
}
"Remove all listeners registered at "+name);
}
}
}
}
/*
* Called when a connector is doing reconnection. Like <code>postReconnection</code>,
* this method is intended to be called only by a client connector:
* <code>RMIConnector</code> and <code>ClientIntermediary</code>.
* Call this method will set the flag beingReconnection to <code>true</code>,
* and the thread used to fetch notifis will be stopped, a new thread can be
* created only after the method <code>postReconnection</code> is called.
*
* It is caller's responsiblity to not re-call this method before calling
* <code>postReconnection</code>.
*/
throw new IOException("Illegal state.");
}
final ClientListenerInfo[] tmp =
beingReconnected = true;
return tmp;
}
/**
* Called after reconnection is finished.
* This method is intended to be called only by a client connector:
* <code>RMIConnector</code> and <code>ClientIntermediary</code>.
*/
throws IOException {
if (state == TERMINATED) {
return;
}
try {
wait();
} catch (InterruptedException ire) {
throw ioe;
}
}
for (int i=0; i<len; i++) {
if (trace) {
"Add a listener at "+
listenerInfos[i].getListenerID());
}
}
beingReconnected = false;
notifyAll();
// only update mbeanRemovedNotifID
try {
} catch (Exception e) {
"Failed to register a listener to the mbean " +
"server: the client will not do clean when an MBean " +
"is unregistered";
}
}
} else {
try {
wait();
} catch (InterruptedException ire) {
throw ioe;
}
}
init(true); // not update clientSequenceNumber
init(false); // need update clientSequenceNumber
}
}
}
public synchronized void terminate() {
if (state == TERMINATED) {
return;
}
}
}
}
// -------------------------------------------------
// private classes
// -------------------------------------------------
//
private volatile boolean alreadyLogged = false;
if (alreadyLogged) return;
// Log only once.
alreadyLogged = true;
}
// Set new context class loader, returns previous one.
// if ctxt is null, log a config message and throw a
// SecurityException.
throw new SecurityException("AccessControlContext must not be null");
}
return AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
try {
// get context class loader - may throw
// SecurityException - though unlikely.
final ClassLoader previous =
// if nothing needs to be done, break here...
// reset context class loader - may throw
// SecurityException
return previous;
} catch (SecurityException x) {
logOnce("Permission to set ContextClassLoader missing. " +
"Notifications will not be dispatched. " +
"Please check your Java policy configuration: " +
x, x);
throw x;
}
}
}, ctxt);
}
public void run() {
final ClassLoader previous;
if (defaultClassLoader != null) {
} else {
}
try {
doRun();
} finally {
if (defaultClassLoader != null) {
}
}
}
private void doRun() {
synchronized (ClientNotifForwarder.this) {
}
}
// nr == null means got exception
final TargetedNotification[] notifs =
final Integer myListenerID;
long missed = 0;
synchronized(ClientNotifForwarder.this) {
// check sequence number.
//
if (clientSequenceNumber >= 0) {
}
for (int i = 0 ; i < len ; i++) {
// check if an mbean unregistration notif
}
continue;
}
if (notif instanceof MBeanServerNotification &&
}
}
}
if (missed > 0) {
"May have lost up to " + missed +
}
// forward
for (int i = 0 ; i < len ; i++) {
}
}
synchronized (ClientNotifForwarder.this) {
}
// tell that the thread is REALLY stopped
try {
} catch (Exception e) {
"removeListenerForMBeanRemovedNotif", e);
}
}
} else {
}
}
"Listener ID not in map");
return;
}
try {
l.handleNotification(notif, h);
} catch (RuntimeException e) {
"Failed to forward a notification " +
"to a listener";
}
}
try {
timeout);
"Got notifications from the server: "+nr);
}
return nr;
} catch (ClassNotFoundException e) {
return fetchOneNotif();
} catch (NotSerializableException e) {
return fetchOneNotif();
} catch (IOException ioe) {
if (!shouldStop()) {
"Failed to fetch notification, " +
}
// no more fetching
return null;
}
}
/* Fetch one notification when we suspect that it might be a
notification that we can't deserialize (because of a
missing class). First we ask for 0 notifications with 0
timeout. This allows us to skip sequence numbers for
notifications that don't match our filters. Then we ask
for one notification. If that produces a
ClassNotFoundException or a NotSerializableException, we
increase our sequence number and ask again. Eventually we
will either get a successful notification, or a return with
0 notifications. In either case we can return a
NotificationResult. This algorithm works (albeit less
well) even if the server implementation doesn't optimize a
request for 0 notifications to skip sequence numbers for
notifications that don't match our filters.
If we had at least one ClassNotFoundException, then we
must emit a JMXConnectionNotification.LOST_NOTIFS.
*/
int notFoundCount = 0;
long firstEarliest = -1;
try {
// 0 notifs to update startSequenceNumber
} catch (ClassNotFoundException e) {
"Impossible exception: " + e);
return null;
} catch (IOException e) {
if (!shouldStop())
return null;
}
if (shouldStop())
return null;
if (firstEarliest < 0)
try {
// 1 notif to skip possible missing class
} catch (Exception e) {
if (e instanceof ClassNotFoundException
|| e instanceof NotSerializableException) {
"Failed to deserialize a notification: "+e.toString());
"Failed to deserialize a notification.", e);
}
} else {
if (!shouldStop())
return null;
}
}
}
if (notFoundCount > 0) {
" because classes were missing locally";
// Even if result.getEarliestSequenceNumber() is now greater than
// it was initially, meaning some notifs have been dropped
// from the buffer, we don't want the caller to see that
// because it is then likely to renotify about the lost notifs.
// So we put back the first value of earliestSequenceNumber
// that we saw.
result = new NotificationResult(
}
}
return result;
}
private boolean shouldStop() {
synchronized (ClientNotifForwarder.this) {
return true;
// no more listener, stop fetching
return true;
}
return false;
}
}
}
// -------------------------------------------------
// private methods
// -------------------------------------------------
if (state == TERMINATED) {
return;
}
this.notifyAll();
}
/*
* Called to decide whether need to start a thread for fetching notifs.
* <P>The parameter reconnected will decide whether to initilize the clientSequenceNumber,
* initilaizing the clientSequenceNumber means to ignore all notifications arrived before.
* If it is reconnected, we will not initialize in order to get all notifications arrived
* during the reconnection. It may cause the newly registered listeners to receive some
* notifications arrived before its registray.
*/
switch (state) {
case STARTED:
return;
case STARTING:
return;
case TERMINATED:
throw new IOException("The ClientNotifForwarder has been terminated.");
case STOPPING:
if (beingReconnected == true) {
// wait for another thread to do, which is doing reconnection
return;
}
try {
wait();
} catch (InterruptedException ire) {
throw ioe;
}
}
// re-call this method to check the state again,
// the state can be other value like TERMINATED.
return;
case STOPPED:
if (beingReconnected == true) {
// wait for another thread to do, which is doing reconnection
return;
}
}
// init the clientSequenceNumber if not reconnected.
if (!reconnected) {
try {
} catch (ClassNotFoundException e) {
// can't happen
}
}
// for cleaning
try {
} catch (Exception e) {
"Failed to register a listener to the mbean " +
"server: the client will not do clean when an MBean " +
"is unregistered";
}
}
// start fetching
return;
default:
// should not
throw new IOException("Unknown state.");
}
}
/**
* Import: should not remove a listener during reconnection, the reconnection
* needs to change the listener list and that will possibly make removal fail.
*/
while (beingReconnected) {
if (state == TERMINATED) {
throw new IOException("Terminated.");
}
try {
wait();
} catch (InterruptedException ire) {
throw ioe;
}
}
if (state == TERMINATED) {
throw new IOException("Terminated.");
}
}
// -------------------------------------------------
// private variables
// -------------------------------------------------
// notif stuff
private final int maxNotifications;
private final long timeout;
// state
/**
* This state means that a thread is being created for fetching and forwarding notifications.
*/
/**
* This state tells that a thread has been started for fetching and forwarding notifications.
*/
/**
* This state means that the fetching thread is informed to stop.
*/
/**
* This state means that the fetching thread is already stopped.
*/
/**
* This state means that this object is terminated and no more thread will be created
* for fetching notifications.
*/
/**
* This variable is used to tell whether a connector (RMIConnector or ClientIntermediary)
* is doing reconnection.
* This variable will be set to true by the method <code>preReconnection</code>, and set
* to false by <code>postReconnection</code>.
* When beingReconnected == true, no thread will be created for fetching notifications.
*/
private boolean beingReconnected = false;
new ClassLogger("javax.management.remote.misc",
"ClientNotifForwarder");
}