/* * 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.WeakHashMap; import net.jini.core.transaction.TransactionException; import net.jini.space.InternalSpaceException; /** * Subclass of QueryWatcher for takes and transactional reads. * Resolves with the first matching transition where the entry is * visible to the associated transaction and the entry is still * available. */ class ConsumingWatcher extends SingletonQueryWatcher implements Transactable { /** * 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; /** * <code>true</code> if this query is a take and * <code>false</code> otherwise. */ private final boolean takeIt; /** * 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; /** * Create a new <code>ConsumingWatcher</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. * @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. * @param takeIt <code>true</code> if this query is a take and * <code>false</code> otherwise. */ ConsumingWatcher(long expiration, long timestamp, long startOrdinal, WeakHashMap provisionallyRemovedEntrySet, Txn txn, boolean takeIt) { super(expiration, timestamp, startOrdinal); this.txn = txn; this.takeIt = takeIt; this.provisionallyRemovedEntrySet = provisionallyRemovedEntrySet; } boolean isInterested(EntryTransition transition, long ordinal) { /* Note, !isResolved() 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. * * Note, we don't bother with isVisible because isAvailable is * the right test for a take, and using isAvailable for read too * simplifies things a bit. The worst that will happen is we * will return true for a transition that should not change * anything - but we are unlikely to get these transitions * (since the fact the transition happened at all suggests * that there is some entry that would have resolved this * watcher), if we do generate a false positive we do a * positive check against the entry in question anyway - so it * can't cause us to produce the wrong answer, and it may well be * that such a "false positive" allows us to get legitimately * resolved anyway - so it is not so "false" a positive after all. */ final TransactableMgr transitionTxn = transition.getTxn(); return (ordinal>startOrdinal) && !isResolved() && transition.isAvailable() && ((null == transitionTxn) || (txn == transitionTxn)); } synchronized void process(EntryTransition transition, long now) { if (isResolved()) return; // Already done. final EntryHandle handle = transition.getHandle(); // Is it still available? if (getServer().attemptCapture(handle, txn, takeIt, null, provisionallyRemovedEntrySet, now, this)) { // Got it resolve(handle, null); } } synchronized boolean catchUp(EntryTransition transition, long now) { if (isResolved()) return true; final TransactableMgr transitionTxn = transition.getTxn(); final EntryHandle handle = transition.getHandle(); /* See note in isInterested about not calling isVisible */ if (transition.isAvailable() && ((null == transitionTxn) || (txn == transitionTxn)) && (getServer().attemptCapture(handle, txn, takeIt, null, provisionallyRemovedEntrySet, now, this))) { // Got it resolve(handle, null); return true; } // Not interesting or could not get it, either way not resolved return false; } /** * 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 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 (!isResolved()) { // Query still in progress, kill it resolve(null, new TransactionException("completed while " + "operation in progress")); } // 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); } }