/*
* 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;
import java.util.Set;
import java.util.Iterator;
import java.util.WeakHashMap;
import net.jini.core.transaction.TransactionException;
import net.jini.space.InternalSpaceException;
/**
* Subclass of <code>QueryWatcher</code> for blocking take multiple
* 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 entries or throwable the query was resolved with.
*/
class TakeMultipleWatcher extends QueryWatcher implements Transactable {
/** The entries (as handles) this watcher has captured */
private final Set handles = new java.util.HashSet();
/** The maxium number of entries that should be captured */
private final int limit;
/**
* Set of entries (represented by <code>EntryHolder</code>s) that
* we would have liked to return, but have been provisionally
* removed.
*/
private final WeakHashMap provisionallyRemovedEntrySet;
/**
* If non-null the transaction this query is
* being performed under. If <code>null</code>
* this query is not associated with a transaction.
*/
private final Txn txn;
/** Set to true when this query is resolved */
private boolean resolved = false;
/**
* If resolved and an exception needs to be thrown the exception
* to throw
*/
private Throwable toThrow;
/**
* The <code>TemplateHandle</code>s associated with this
* watcher.
*/
private Set owners = new java.util.HashSet();
/**
* The OutriggerServerImpl we are part of.
*/
private OutriggerServerImpl server;
/**
* <code>true</code> if we have processed the transition that
* occurred during the initial search and are now blocking waiting
* for a match to appear
*/
private boolean blocking = false;
/**
* Create a new <code>TakeMultipleWatcher</code>.
* @param limit the maximum number of entries that should
* be captured by this watcher
* @param expiration the initial expiration time
* for this <code>TakeMultipleWatcher</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.
* @param provisionallyRemovedEntrySet If the watcher encounters
* an entry that can not be read/taken because it has been
* provisionally removed then its handle will be placed in
* this <code>WeakHashMap</code> as a key (with null as the
* value). May be <code>null</code> in which case
* provisionally removed entries will not be
* recorded. Ensures that object is only accessed by one
* thread at a time
* @param txn If the query is being performed under
* a transaction the <code>Txn</code> object
* associated with that transaction. */
TakeMultipleWatcher(int limit, long expiration, long timestamp,
long startOrdinal, WeakHashMap provisionallyRemovedEntrySet, Txn txn)
{
super(expiration, timestamp, startOrdinal);
this.limit = limit;
this.provisionallyRemovedEntrySet = provisionallyRemovedEntrySet;
this.txn = txn;
}
/**
* Associate a <code>TemplateHandle</code> with this object. May
* be called more than once.
*
* @param h The <code>TemplateHandle</code> to associate
* 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>
*/
synchronized boolean addTemplateHandle(TemplateHandle h) {
if (owners == null)
return false; // Already removed!
owners.add(h);
if (server == null)
server = h.getServer();
return true;
}
synchronized boolean catchUp(EntryTransition transition, long now) {
if (resolved)
return true;
final TransactableMgr transitionTxn = transition.getTxn();
final EntryHandle handle = transition.getHandle();
if (handles.contains(handle))
return false; // Already got it
if (transition.isAvailable() &&
((null == transitionTxn) || (txn == transitionTxn)) &&
(server.attemptCapture(handle, txn, true, null,
provisionallyRemovedEntrySet, now, this)))
{
// Got it
captured(handle);
return resolved;
}
// Not interesting, or could not get it, either way not resolved
return false;
}
boolean isInterested(EntryTransition transition, long ordinal) {
/* Note, !resolved without the lock will result only in
* false positives, not false negatives - it will only
* cause isInterested() to return false if we are resolved,
* we may still return true if we are resolved though.
*/
final TransactableMgr transitionTxn = transition.getTxn();
return (ordinal>startOrdinal) && !resolved &&
transition.isAvailable() &&
((null == transitionTxn) || (txn == transitionTxn));
}
synchronized void process(EntryTransition transition, long now) {
if (resolved)
return; // Already done.
final EntryHandle handle = transition.getHandle();
if (handles.contains(handle))
return; // Already got it
// Is it still available?
if (server.attemptCapture(handle, txn, true, null,
provisionallyRemovedEntrySet, now, this))
{
// Got it
captured(handle);
}
}
void waitOnResolution() throws InterruptedException {
synchronized (this) {
blocking = true;
if (handles.size() > 0) {
resolved = true;
} else {
while (!resolved) {
final long sleepTime =
getExpiration() - System.currentTimeMillis();
if (sleepTime <= 0) {
// All done
resolved = true;
} else {
wait(sleepTime);
}
}
}
for (Iterator i=owners.iterator(); i.hasNext(); ) {
final TemplateHandle h = (TemplateHandle)i.next();
h.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[] resolvedWithEntries() {
if (!resolved)
throw new IllegalStateException("Query not yet resolved");
if (handles.isEmpty())
return null;
final EntryHandle[] rslt = new EntryHandle[handles.size()];
return (EntryHandle[])handles.toArray(rslt);
}
/**
* 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;
}
boolean isResolved() {
return resolved;
}
/**
* Mark this query as resolved. This method assumes
* the calling thread own the lock on this object.
* @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.
*/
private void captured(EntryHandle handle) {
handles.add(handle);
if (handles.size() == limit || blocking) {
resolved = true;
notifyAll();
}
assert (handles.size() <= limit);
}
/**
* If a transaction ends in the middle of a query we want
* to throw an exception to the client making the query
* (not to the <code>Txn</code> calling us here.)
*/
public synchronized int prepare(TransactableMgr mgr,
OutriggerServerImpl space)
{
assert txn != null:"Transactable method called on a " +
"non-transactional ConsumingWatcher";
// only throw an exception if we are not resolved.
if (!resolved) {
// Query still in progress, kill it
resolved = true;
toThrow = new TransactionException("completed while " +
"operation in progress");
notifyAll();
/* If we have a partial result we could try to return it,
* but if we are a persistent space the attempt to log
* the take will fail with a CannotJoinException, so it
* seems cleaner to just fail here (same thing might happen
* if we are resolved, but once we think we are resolved
* and may have returned from waitOnResolution seem
* cleaner to not try and set toThrow).
*/
}
// If this object has made changes they have been recorded elsewhere
return NOTCHANGED;
}
/**
* This should never happen since we always return
* <code>NOTCHANGED</code> from <code>prepare</code>. */
public void commit(TransactableMgr mgr, OutriggerServerImpl space) {
throw new InternalSpaceException("committing a blocking query");
}
/**
* If a transaction ends in the middle of a query we want
* to throw an exception to the client making the query
* (not the <code>Txn</code> calling us here.)
*/
public void abort(TransactableMgr mgr, OutriggerServerImpl space) {
// prepare does the right thing, and should forever
prepare(mgr, space);
}
}