Package net.sf.katta.client

Source Code of net.sf.katta.client.ClientResult$Entry

/**
* Copyright 2008 the original author or authors.
*
* Licensed 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 net.sf.katta.client;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.katta.util.KattaException;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
* A multithreaded destination for results and/or errors. Results are produced
* by nodes and we pass lists of shards to nodes. But due to replication and
* retries, we associate sets of shards with the results, not nodes.
*
* Multiple NodeInteractions will be writing to this object at the same time. If
* not closed, expect the contents to change. For example isComplete() might
* return false and then a call to getResults() might return a complete set (in
* which case another call to isComplete() would return true). If you need
* complex state information, rather than making multiple calls, you should use
*
*
*
* You can get these results from a WorkQueue by polling or blocking. Once you
* have an ClientResult instance you may poll it or block on it. Whenever
* resutls or errors are added notifyAll() is called. The ClientResult can
* report on the number or ratio of shards completed. You can stop the search by
* calling close(). The ClientResult will no longer change, and any outstanding
* threads will be killed (via notification to the provided IClosedListener).
*/
public class ClientResult<T> implements IResultReceiver<T>, Iterable<ClientResult<T>.Entry> {

  private static final Logger LOG = Logger.getLogger(ClientResult.class);

  /**
   * Immutable storage of either a result or an error, which shards produced it,
   * and it's arrival time.
   */
  public class Entry {

    public final T result;
    public final Throwable error;
    public final Set<String> shards;
    public final long time;

    @SuppressWarnings("unchecked")
    private Entry(Object o, Collection<String> shards, boolean isError) {
      this.result = !isError ? (T) o : null;
      this.error = isError ? (Throwable) o : null;
      this.shards = Collections.unmodifiableSet(new HashSet<String>(shards));
      this.time = System.currentTimeMillis();
    }

    @Override
    public String toString() {
      String resultStr;
      if (result != null) {
        resultStr = "null";
        if (result != null) {
          try {
            resultStr = result.toString();
          } catch (Throwable t) {
            LOG.trace("Error calling toString() on result", t);
            resultStr = "(toString() err)";
          }
        }
        if (resultStr == null) {
          resultStr = "(null toString())";
        }
      } else {
        resultStr = error != null ? error.getClass().getSimpleName() : "null";
      }
      return String.format("%s from %s at %d", resultStr, shards, time);
    }
  }

  /**
   * Provides a way to notify interested parties when our close() method is
   * called.
   */
  public interface IClosedListener {
    /**
     * The ClientResult's close() method was called. The result is closed before
     * calling this.
     */
    public void clientResultClosed();
  }

  private boolean closed = false;
  private final Set<String> allShards;
  private final Set<String> seenShards = new HashSet<String>();
  private final Set<Entry> entries = new HashSet<Entry>();
  private final Map<Object, Entry> resultMap = new HashMap<Object, Entry>();
  private final Collection<T> results = new ArrayList<T>();
  private final Collection<Throwable> errors = new ArrayList<Throwable>();
  private final long startTime = System.currentTimeMillis();
  private final IClosedListener closedListener;

  /**
   * Construct a non-closed ClientResult, which waits for addResults() or
   * addError() calls until close() is called. After that point, addResults()
   * and addError() calls are ignored, and this object becomes immutable.
   *
   * @param closedListener
   *          If not null, it's clientResultClosed() method is called when our
   *          close() method is.
   * @param allShards
   *          The set of all shards to expect results from.
   */
  public ClientResult(IClosedListener closedListener, Collection<String> allShards) {
    if (allShards == null || allShards.isEmpty()) {
      throw new IllegalArgumentException("No shards specified");
    }
    this.allShards = Collections.unmodifiableSet(new HashSet<String>(allShards));
    this.closedListener = closedListener;
    if (LOG.isTraceEnabled()) {
      LOG.trace(String.format("Created ClientResult(%s, %s)", closedListener != null ? closedListener : "null",
              allShards));
    }
  }

  /**
   * Construct a non-closed ClientResult, which waits for addResults() or
   * addError() calls until close() is called. After that point, addResults()
   * and addError() calls are ignored, and this object becomes immutable.
   *
   * @param closedListener
   *          If not null, it's clientResultClosed() method is called when our
   *          close() method is.
   * @param allShards
   *          The set of all shards to expect results from.
   */
  public ClientResult(IClosedListener closedListener, String... allShards) {
    this(closedListener, Arrays.asList(allShards));
  }

  /**
   * Add a result. Will be ignored if closed.
   *
   * @param result
   *          The result to add.
   * @param shards
   *          The shards used to compute the result.
   */
  public void addResult(T result, Collection<String> shards) {
    if (closed) {
      if (LOG.isTraceEnabled()) {
        LOG.trace("Ignoring results given to closed ClientResult");
      }
      return;
    }
    if (shards == null) {
      LOG.warn("Null shards passed to AddResult()");
      return;
    }
    Entry entry = new Entry(result, shards, false);
    if (entry.shards.isEmpty()) {
      LOG.warn("Empty shards passed to AddResult()");
      return;
    }
    synchronized (this) {
      if (LOG.isTraceEnabled()) {
        LOG.trace(String.format("Adding result %s", entry));
      }
      if (LOG.isEnabledFor(Level.WARN)) {
        for (String shard : entry.shards) {
          if (seenShards.contains(shard)) {
            LOG.warn("Duplicate occurances of shard " + shard);
          } else if (!allShards.contains(shard)) {
            LOG.warn("Unknown shard " + shard + " returned results");
          }
        }
      }
      entries.add(entry);
      seenShards.addAll(entry.shards);
      if (result != null) {
        results.add(result);
        resultMap.put(result, entry);
      }
      notifyAll();
    }
  }

  /**
   * Add a result. Will be ignored if closed.
   *
   * @param result
   *          The result to add.
   * @param shards
   *          The shards used to compute the result.
   */
  public void addResult(T result, String... shards) {
    addResult(result, Arrays.asList(shards));
  }

  /**
   * Add an error. Will be ignored if closed.
   *
   * @param error
   *          The error to add.
   * @param shards
   *          The shards used when the error happened.
   */
  public void addError(Throwable error, Collection<String> shards) {
    if (closed) {
      if (LOG.isTraceEnabled()) {
        LOG.trace("Ignoring exception given to closed ClientResult");
      }
      return;
    }
    if (shards == null) {
      LOG.warn("Null shards passed to addError()");
      return;
    }
    Entry entry = new Entry(error, shards, true);
    if (entry.shards.isEmpty()) {
      LOG.warn("Empty shards passed to addError()");
      return;
    }
    synchronized (this) {
      if (LOG.isTraceEnabled()) {
        LOG.trace(String.format("Adding error %s", entry));
      }
      if (LOG.isEnabledFor(Level.WARN)) {
        for (String shard : entry.shards) {
          if (seenShards.contains(shard)) {
            LOG.warn("Duplicate occurances of shard " + shard);
          } else if (!allShards.contains(shard)) {
            LOG.warn("Unknown shard " + shard + " returned results");
          }
        }
      }
      entries.add(entry);
      seenShards.addAll(entry.shards);
      if (error != null) {
        errors.add(error);
        resultMap.put(error, entry);
      }
      notifyAll();
    }
  }

  /**
   * Add an error. Will be ignored if closed.
   *
   * @param error
   *          The error to add.
   * @param shards
   *          The shards used when the error happened.
   */
  public synchronized void addError(Throwable error, String... shards) {
    addError(error, Arrays.asList(shards));
  }

  /**
   * Stop accepting additional results or errors. Become an immutable object.
   * Also report the closure to the IClosedListener passed to our constructor,
   * if any. Normally this will tell the WorkQueue to shut down immediately,
   * killing any still running threads.
   */
  public synchronized void close() {
    LOG.trace("close() called.");
    if (!closed) {
      closed = true;
      if (closedListener != null) {
        LOG.trace("Notifying closed listener.");
        closedListener.clientResultClosed();
      }
    }
    notifyAll();
  }

  /**
   * Is this result set closed, and therefore not accepting any additional
   * results or errors. Once closed, this becomes an immutable object.
   */
  public boolean isClosed() {
    return closed;
  }

  /**
   * @return the set of all shards we are expecting results from.
   */
  public Set<String> getAllShards() {
    return allShards;
  }

  /**
   * @return the set of shards from whom we have seen either results or errors.
   */
  public synchronized Set<String> getSeenShards() {
    return Collections.unmodifiableSet(closed ? seenShards : new HashSet<String>(seenShards));
  }

  /**
   * @return the subset of all shards from whom we have not seen either results
   *         or errors.
   */
  public synchronized Set<String> getMissingShards() {
    Set<String> missing = new HashSet<String>(allShards);
    missing.removeAll(seenShards);
    return missing;
  }

  /**
   * @return all of the results seen so far. Does not include errors.
   */
  public synchronized Collection<T> getResults() {
    return Collections.unmodifiableCollection(closed ? results : new ArrayList<T>(results));
  }

  /**
   * Either return results or throw an exception. Allows simple one line use of
   * a ClientResult. If no errors occurred, returns same results as
   * getResults(). If any errors occurred, one is chosen via getError() and
   * thrown.
   *
   * @return if no errors occurred, results via getResults().
   * @throws Throwable
   *           if any errors occurred, via getError().
   */
  public synchronized Collection<T> getResultsOrThrowException() throws Throwable {
    if (isError()) {
      throw getError();
    } else {
      return getResults();
    }
  }

  /**
   * Either return results or throw a KattaException. Allows simple one line use
   * of a ClientResult. If no errors occurred, returns same results as
   * getResults(). If any errors occurred, one is chosen via getKattaException()
   * and thrown.
   *
   * @return if no errors occurred, results via getResults().
   * @throws KattaException
   *           if any errors occurred, via getError().
   */
  public synchronized Collection<T> getResultsOrThrowKattaException() throws KattaException {
    if (isError()) {
      throw getKattaException();
    } else {
      return getResults();
    }
  }

  /**
   * @return all of the errors seen so far.
   */
  public synchronized Collection<Throwable> getErrors() {
    return Collections.unmodifiableCollection(closed ? errors : new ArrayList<Throwable>(errors));
  }

  /**
   * @return a randomly chosen error, or null if none exist.
   */
  public synchronized Throwable getError() {
    for (Entry e : entries) {
      if (e.error != null) {
        return e.error;
      }
    }
    return null;
  }

  /**
   * @return a randomly chosen KattaException if one exists, else a
   *         KattaException wrapped around a randomly chosen error if one
   *         exists, else null.
   */
  public synchronized KattaException getKattaException() {
    Throwable error = null;
    for (Entry e : this) {
      if (e.error != null) {
        if (e.error instanceof KattaException) {
          return (KattaException) e.error;
        } else {
          error = e.error;
        }
      }
    }
    if (error != null) {
      return new KattaException("Error", error);
    } else {
      return null;
    }
  }

  /**
   * @param result
   *          The result to look up.
   * @return What shards produced the result, and when it arrived. Returns null
   *         if result not found.
   */
  public synchronized Entry getResultEntry(T result) {
    return resultMap.get(result);
  }

  /**
   * @param error
   *          The error to look up.
   * @return What shards produced the error, and when it arrived. Returns null
   *         if error not found.
   */
  public synchronized Entry getErrorEntry(Throwable error) {
    return resultMap.get(error);
  }

  /**
   * @return true if we have seen either a result or an error for all shards.
   */
  public synchronized boolean isComplete() {
    return seenShards.containsAll(allShards);
  }

  /**
   * @return true if any errors were reported.
   */
  public synchronized boolean isError() {
    return !errors.isEmpty();
  }

  /**
   * @return true if result is complete (all shards reporting in) and no errors
   *         occurred.
   */
  public synchronized boolean isOK() {
    return isComplete() && !isError();
  }

  /**
   * @return the ratio (0.0 .. 1.0) of shards we have seen. 0.0 when no shards,
   *         1.0 when complete.
   */
  public synchronized double getShardCoverage() {
    int seen = seenShards.size();
    int all = allShards.size();
    return all > 0 ? (double) seen / (double) all : 0.0;
  }

  /**
   * @return the time when this ClientResult was created.
   */
  public long getStartTime() {
    return startTime;
  }

  /**
   * @return a snapshot of all the data about the results so far.
   */
  public Set<Entry> entrySet() {
    if (closed) {
      return Collections.unmodifiableSet(entries);
    } else {
      synchronized (this) {
        // Set will keep changing, make a snapshot.
        return Collections.unmodifiableSet(new HashSet<Entry>(entries));
      }
    }
  }

  /**
   * @return an iterator of our Entries sees so far.
   */
  public Iterator<Entry> iterator() {
    return entrySet().iterator();
  }

  /**
   * @return a list of our results or errors, in the order they arrived.
   */
  public List<Entry> getArrivalTimes() {
    List<Entry> arrivals;
    synchronized (this) {
      arrivals = new ArrayList<Entry>(entries);
    }
    Collections.sort(arrivals, new Comparator<Entry>() {
      public int compare(Entry o1, Entry o2) {
        if (o1.time != o2.time) {
          return o1.time < o2.time ? -1 : 1;
        } else {
          // Break ties in favor of results.
          if (o1.result != null && o2.result == null) {
            return -1;
          } else if (o2.result != null && o1.result == null) {
            return 1;
          } else {
            return 0;
          }
        }
      }
    });
    return arrivals;
  }

  public void waitFor(IResultPolicy<T> policy) {
    long waitTime = 0;
    while (true) {
      synchronized (results) {
        // Need to stay synchronized before waitTime() through wait() or we will
        // miss notifications.
        waitTime = policy.waitTime(this);
        if (waitTime > 0 && !closed) {
          if (LOG.isTraceEnabled()) {
            LOG.trace(String.format("Waiting %d ms, results = %s", waitTime, this));
          }
          try {
            synchronized (this) {
              this.wait(waitTime);
            }
          } catch (InterruptedException e) {
            LOG.debug("Interrupted", e);
          }
          if (LOG.isTraceEnabled()) {
            LOG.trace(String.format("Done waiting, results = %s", this));
          }
        } else {
          break;
        }
      }
    }
    if (waitTime < 0) {
      close();
    }
  }

  @Override
  public synchronized String toString() {
    int numResults = 0;
    int numErrors = 0;
    for (Entry e : this) {
      if (e.result != null) {
        numResults++;
      }
      if (e.error != null) {
        numErrors++;
      }
    }
    return String.format("ClientResult: %d results, %d errors, %d/%d shards%s%s", numResults, numErrors, seenShards
            .size(), allShards.size(), closed ? " (closed)" : "", isComplete() ? " (complete)" : "");
  }

}
TOP

Related Classes of net.sf.katta.client.ClientResult$Entry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.