/*
* 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.outrigger;
/**
* Subclass of <code>QueryWatcher</code> for singleton queries. Most
* of the usage model is laid out in <code>QueryWatcher</code> except
* how the result of the query is obtained from the
* watcher. <code>SingletonQueryWatcher</code> defines the
* <code>resolvedWithEntry</code> and
* <code>resolvedWithThrowable</code> methods which can be used to
* obtain the entry or throwable the query was resolved with (and in
* the case of <code>IfExistsWatcher</code>s
* <code>isConflictSetEmpty</code>).
*/
abstract class SingletonQueryWatcher extends QueryWatcher {
/** Set to true when this query is resolved */
private boolean resolved = false;
/** If resolved and an entry was found the entry to return */
private EntryHandle handle;
/**
* If resolved and an exception needs to be thrown the exception
* to throw
*/
private Throwable toThrow;
/**
* The <code>TemplateHandle</code> associated with this
* watcher.
*/
private TemplateHandle owner;
/**
* Create a new <code>SingletonQueryWatcher</code>.
* @param expiration the initial expiration time
* for this <code>TransitionWatcher</code> in
* milliseconds since the beginning of the epoch.
* @param timestamp the value that is used
* to sort <code>TransitionWatcher</code>s.
* @param startOrdinal the highest ordinal associated
* with operations that are considered to have occurred
* before the operation associated with this watcher.
*/
SingletonQueryWatcher(long expiration, long timestamp, long startOrdinal) {
super(expiration, timestamp, startOrdinal);
}
/**
* Associate a <code>TemplateHandle</code> with this object. May
* only be called once on any given <code>SingletonQueryWatcher</code>
* instance.
*
* @param h The <code>TemplateHandle</code> associated
* with this watcher.
* @return <code>true</code> if the handle was succfully added,
* and <code>false</code> if the watcher has already
* been removed.
* @throws NullPointerException if <code>h</code> is
* <code>null</code>
*/
boolean addTemplateHandle(TemplateHandle h) {
if (h == null)
throw
new NullPointerException("TemplateHandle must be non-null");
if (owner != null)
throw new AssertionError("Can only call addTemplateHandle once");
owner = h;
return true;
}
/**
* Block until the query this object represents is resolved. If the
* query is already resolved, return immediately. This method must be
* called even if it is know that the query has been resolved.
* This method should be called exactly once.
*/
void waitOnResolution() throws InterruptedException {
final TemplateHandle owner;
synchronized (this) {
while (!resolved) {
final long sleepTime =
getExpiration() - System.currentTimeMillis();
if (sleepTime <= 0) {
// All done
resolved = true;
} else {
wait(sleepTime);
}
}
owner = this.owner;
// Signal that we (well will be soon) removed from owner
this.owner = null;
}
// If we are called exactly once, owner should never be null
owner.removeTransitionWatcher(this);
}
/**
* If the query has been resolved by finding an matching entry,
* returns the <code>EntryHandle</code> for that entry. If the query has
* been resolved but no entry is available (e.g. the expiration time has
* been reached or an exception needs to be thrown) returns
* <code>null</code>. Note, once resolution has been reached this
* method can only return non-null if <code>resolvedWithThrowable</code>
* returns <code>null</code>.
*
* @return The entry to be returned, or <code>null</code> if
* no entry is available.
* @throws IllegalStateException if the query has not
* yet been resolved.
*/
synchronized EntryHandle resolvedWithEntry() {
if (!resolved)
throw new IllegalStateException("Query not yet resolved");
return handle;
}
/**
* If the query has been resolved with an exceptional condition,
* the exception that should be thrown to the client. Returns
* <code>null</code> otherwise. Note, once resolution has been
* reached this method can only return non-null if
* <code>resolvedWithEntry</code> returns <code>null</code>.
* @return the exception (if any) that should
* be thrown to the client.
* @throws IllegalStateException if the query has not
* yet been resolved.
*/
synchronized Throwable resolvedWithThrowable() {
if (!resolved)
throw new IllegalStateException("Query not yet resolved");
return toThrow;
}
/**
* Returns <code>true</code> if this query has been resolved. If the
* calling thread is owns the lock on this object the answer is
* definitive. If the lock is not held only a <code>true</code> answer
* can be considered definitive.
* @return <code>true</code> if the query has been
* resolved, <code>false</code> otherwise.
*/
boolean isResolved() {
return resolved;
}
/**
* Mark this query as resolved. This method assumes
* the calling thread own the lock on this object.
* This method is intended only for use by subclasses.
* @param handle If being resolved by finding an entry
* the entry which was found and that should be returned
* by <code>resolvedWithEntry</code>. Otherwise should be
* <code>null</code>. May only be non-null if throwable
* is <code>null</code>.
* @param throwable If being resolved by an exception
* the throwable to be thrown and that should be returned
* by <code>resolvedWithThrowable</code> otherwise should
* be <code>null</code>. May only be non-null if entry is
* <code>null</code>.
* @throws IllegalArgumentException if both
* <code>entry</code> and <code>throwable</code>
* are non-null.
* @throws IllegalStateException if the query has already
* been resolved.
*/
void resolve(EntryHandle handle, Throwable throwable) {
assert Thread.holdsLock(this) : "Caller of resolve() must hold lock";
if (resolved)
throw new IllegalStateException(
"Can't call resolve on a resolved query ");
if ((this.handle != null) || (toThrow != null))
throw new IllegalStateException(
"At lease one argument must be null");
resolved = true;
this.handle = handle;
toThrow = throwable;
notifyAll();
}
/**
* Method to give sub-classes access to OutriggerServerImpl
* Assumes caller owns lock on <code>this</code> and that
* this watcher has not been removed.
*/
OutriggerServerImpl getServer() {
assert Thread.holdsLock(this) : "getServer() called without lock";
return owner.getServer();
}
}