spark-spi/src/main/java/spark/spi/util/DateTime.java
public class DateTime {

  // Zulu timezone; will use this by default.
  private static final TimeZone ZULU_TZ = TimeZone.getTimeZone("GMT+00:00");
  
  // Local timezone.
  private static final TimeZone LOCAL_TZ = TimeZone.getDefault();
  
  // Used for writing the milliseconds field.
  private static final int[] TENS = { 1, 10, 100 };
  
  private static final int FIRST_MONTH = 1; // January
  private static final int LAST_MONTH = 12; // December
  
  // Special month for leap years.
  private static final int FEBRUARY = 2;
  
  // The days for each month (zero-indexed)
  private static final int [] DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  
  // The maximum year which will fit into a java.util.Date
  private static final int MAX_YEAR = 292277023;
  
  // Conversion factors.
  private static final int DAYS_IN_YEAR = 365;
  private static final int MINS_IN_HOUR = 60;
  private static final int MS_IN_SEC = 1000;
  private static final int MS_IN_MIN = 60000;
  private static final int MS_IN_HOUR = 3600000;
  private static final int MS_IN_DAY = 86400000;
  
  // Offset between the Java epoch (1970-01-01T00:00:00Z) and the ISO-8601 epoch (0001-01-01T00:00:00Z)
  private static final long EPOCH_OFFSET_MS = elapsedDays(1970) * MS_IN_DAY;
  
  // Separators in the lexical string.
  private static final char DATE_SEP = '-';
  private static final char DATE_TIME_SEP = 'T';
  private static final char TIME_SEP = ':';
  
  /**
   * Formats the given date as an XSD <tt>dateTime</tt> lexical string, using Zulu time.
   * @param d A date.
   * @return The corresponding lexical string.
   */
  public static String format(Date d) {
    return format(d, ZULU_TZ);
  }
  
  /**
   * <p>
   * Formats the given date as an XSD <tt>dateTime</tt> lexical string, using the offset specified
   * by the given timezone.
   * </p>
   * 
   * <p>
   * If no timezone is specified, then the given date is treated as a local time; it will be written
   * using the offset of the system default timezone, but the timezone offset will be omitted from
   * the lexical string. This is <strong>not</strong> recommended, as the resulting <tt>dateTime</tt>
   * will be relative to some unknown timezone and could be interpreted differently by different systems.
   * </p>
   * 
   * @param d A date.
   * @param tz A timezone.
   * @return The corresponding lexical string.
   */
  public static String format(Date d, TimeZone tz) {
    if (d == null) throw new IllegalArgumentException("Null date value");
    
    long offset = d.getTime();
    int tzOffsetMs = (tz != null ? tz : LOCAL_TZ).getOffset(d.getTime());
    if (offset > Long.MAX_VALUE - EPOCH_OFFSET_MS - tzOffsetMs) throw new ArithmeticException("Cannot convert to ISO-8601 offset.");
    offset += EPOCH_OFFSET_MS + tzOffsetMs;
    
    long da = offset / MS_IN_DAY;
    int millis = (int)(offset - (da * MS_IN_DAY));
    // Adjust for negative offsets.
    if (millis < 0) {
      da--;
      millis += MS_IN_DAY;
    }
    
    int year = (int)(da * 400 / 146097) + 1; // == da / 365.2425 + 1
    int day = (int)(da - elapsedDays(year)) + 1;
    while (day < 1) day += daysInYear(--year);
    int temp;
    while (day > (temp = daysInYear(year))) {
      day -= temp;
      year++;
    }
    
    int month = FIRST_MONTH;
    while (day > (temp = daysInMonth(year, month))) {
      day -= temp;
      month++;
    }
    
    int hour = millis / MS_IN_HOUR;
    millis -= hour * MS_IN_HOUR;
    int min = millis / MS_IN_MIN;
    millis -= min * MS_IN_MIN;
    int sec = millis / MS_IN_SEC;
    millis -= sec * MS_IN_SEC;

    StringBuilder sb = new StringBuilder();
    if (year < 0) {
      sb.append('-');
      year = -year;
    }
    
    append(sb, year, 4);
    sb.append(DATE_SEP);
    append(sb, month, 2);
    sb.append(DATE_SEP);
    append(sb, day, 2);
    sb.append(DATE_TIME_SEP);
    append(sb, hour, 2);
    sb.append(TIME_SEP);
    append(sb, min, 2);
    sb.append(TIME_SEP);
    append(sb, sec, 2);
    
    if (millis > 0) {
      sb.append('.');
      for (int i = 2; i >= 0 && millis > 0; i--) {
        sb.append(millis / TENS[i]);
        millis = millis % TENS[i];
      }
    }
    
    if (tz != null) {
      if (tzOffsetMs == 0) {
        sb.append('Z');
      } else {
        int tzOffset = tzOffsetMs / MS_IN_MIN; // XSD specifies minutes.
        sb.append(tzOffset < 0 ? '-' : '+');
        tzOffset = Math.abs(tzOffset);
        append(sb, tzOffset / MINS_IN_HOUR, 2);
        sb.append(TIME_SEP);
        append(sb, tzOffset % MINS_IN_HOUR, 2);
      }
    }
    return sb.toString();
  }
  
  /** Append the given value to the string builder, with leading zeros to result in the given minimum width. */
  private static void append(StringBuilder sb, int val, int width) {
    String s = Integer.toString(val);
    for (int i = s.length(); i < width; i++) sb.append('0');
    sb.append(s);
  }
  
  /**
   * Parse an XSD <tt>dateTime</tt> lexical string to a Java date, using strict parsing.
   * @param str The lexical string.
   * @return The corresponding Java date.
   * @throws DateFormatException if the string could not be parsed as an XSD <tt>dateTime</tt>.
   * @throws ArithmeticException if the date represented by the string will not fit into a Java date.
   */
  public static Date parse(String str) {
    return parse(str, true);
  }
  
  /**
   * Parse an XSD <tt>dateTime</tt> lexical string to a Java date. If <tt>strict</tt> is true, then
   * exceptions will be thrown if the lexical string does not exactly match the form specified by
   * XSD. If <tt>strict</tt> is false, then these constraints will be relaxed somewhat to allow such
   * things as alternate delimiters, omitting leading zeros, and out-of-range values for certain
   * fields (e.g. '2011-07-32' will be treated as the first day of the following month). 
   * @param str The lexical string.
   * @param strict Flag to indicate strict or lenient parsing.
   * @return The corresponding Java date.
   * @throws DateFormatException if the string could not be parsed as an XSD <tt>dateTime</tt>.
   * @throws ArithmeticException if the date represented by the string will not fit into a Java date.
   */
  public static Date parse(String str, boolean strict) {
    if (str != null) str = str.trim();
    if (str == null || str.isEmpty()) throw new IllegalArgumentException("String must be non-empty");

    Input s = new Input(str);
    boolean yrNeg = s.getChar() == '-';
    if (yrNeg) s.index++;
    
    int year = parseField("year", s, DATE_SEP, 4, 0, strict);
    // Always validate the year, or it won't fit in a long.
    if (year > MAX_YEAR) throw new ArithmeticException("The given year (" + (yrNeg ? "-" : "") + year + ") exceeds the limit for java.util.Date");
    if (yrNeg) year = -year;
    
    int month = parseField("month", s, DATE_SEP, 2, 2, strict);
    // Always validate the month
    if (month < FIRST_MONTH || month > LAST_MONTH) throw new DateFormatException("month out of range [1..12]", s.str, s.index - 2);
    
    int day = parseField("day", s, DATE_TIME_SEP, 2, 2, strict);
    if (strict && (day < 0 || day > daysInMonth(year, month))) throw new DateFormatException("day out of range [1.." + daysInMonth(year, month) + "]", s.str, s.index - 2);
    
    int hour = parseField("hour", s, TIME_SEP, 2, 2, strict);
    if (strict && hour > 24) throw new DateFormatException("hour out of range [0..24]", s.str, s.index - 2);
    
    int min = parseField("minute", s, TIME_SEP, 2, 2, strict);
    if (strict && min > 59) throw new DateFormatException("minutes out of range [0..59]", s.str, s.index - 2);
    
    int sec = parseField("second", s, null, 2, 2, strict);
    if (strict && sec > 59) throw new DateFormatException("seconds out of range [0..59]", s.str, s.index - 1);
    
    int millis = parseMillis(s);
    if (strict && hour == 24 && (min > 0 || sec > 0 || millis > 0)) {
      throw new DateFormatException("midnight indicator may not include non-zero minutes or seconds", s.str, s.index - 1);
    }
    
    Integer tzOffsetMs = parseTzOffsetMs(s, strict);
    boolean local = tzOffsetMs == null;
    if (local) tzOffsetMs = 0;
    if (strict && s.index < s.len) throw new DateFormatException("unexpected content, expected EOF", s.str, s.index);
    
    // Calculate the number of days elapsed between the epoch and the start of this day.
    long da = elapsedDays(year);
    for (int m = FIRST_MONTH; m < month; m++) {
      da += daysInMonth(year, m);
    }
    da += (day - 1);
    
    // Calculate the number of milliseconds elapsed between the epoch and the given time, accounting for timezone offset.
    long ms = da * MS_IN_DAY + hour * MS_IN_HOUR + min * MS_IN_MIN + sec * MS_IN_SEC + millis - tzOffsetMs;
    
    // Translate from ISO-8601 epoch to Java Date epoch.
    if (ms < (Long.MIN_VALUE + EPOCH_OFFSET_MS)) throw new ArithmeticException("The specified date will not fit into java.util.Date: " + str);
    ms -= EPOCH_OFFSET_MS;
    
    // Handle local timezone.
    if (local) {
      tzOffsetMs = LOCAL_TZ.getOffset(ms);
      if (tzOffsetMs > 0 && ms < (Long.MIN_VALUE + tzOffsetMs)) throw new ArithmeticException("The specified date will not fit into java.util.Date: " + str);
      ms -= tzOffsetMs;
    }
    
    return new Date(ms);
  }
  
  /** Find the number of elapsed days from the epoch to the beginning of the given year. */
  private static long elapsedDays(int year) {
    int y = year - 1;
    return DAYS_IN_YEAR * (long)y + div(y, 400) - div(y, 100) + div(y, 4);
  }
  
  /**
   * Implementation of the XSD div operation, which differs from Java integer division in that it rounds
   * down for negative quotients while Java rounds up, i.e.:
   *   -1 / 2 == 0
   *   div(-1, 2) == -1
   */
  private static int div(int a, int b) {
    return a > 0 ? (b > 0 ? a / b : (a - b - 1) / b)
                 : (b > 0 ? (a - b + 1) / b : a / b);
  }
  
  /** Find the number of days in the given month, given the year. */
  private static int daysInMonth(int year, int month) {
    assert month >= FIRST_MONTH && month <= LAST_MONTH;
    int d = DAYS_IN_MONTH[month - 1];
    if (month == FEBRUARY && isLeapYear(year)) d++;
    return d;
  }
  
  /** Find the number of days in the given year, taking leap years into account. */
  private static int daysInYear(int year) {
    return isLeapYear(year) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
  }
  
  /** Determine whether the given year is a leap year. */
  static boolean isLeapYear(int year) {
    return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
  }
  
  /** Parse the fractional seconds field from the input, returning the number of milliseconds and truncating extra places. */
  private static int parseMillis(Input s) {
    if (s.index < s.len && s.getChar() == '.') {
      int startIndex = ++s.index;
      int ms = parseInt(s);
      int len = s.index - startIndex;
      for (; len < 3; len++) ms *= 10;
      for (; len > 3; len--) ms /= 10; // truncate because it's easier than rounding.
      return ms;
    }
    return 0;
  }
  
  /** Parse the timezone offset from the input, returning its millisecond value. */
  private static Integer parseTzOffsetMs(Input s, boolean strict) {
    if (s.index < s.len) {
      char c = s.getChar();
      s.index++;
      int sign;
      if (c == 'Z') {
        return 0;
      } else if (c == '+') {
        sign = 1;
      } else if (c == '-') {
        sign = -1;
      } else {
        throw new DateFormatException("unexpected character, expected one of [Z+-]", s.str, s.index - 1);
      }
      int tzHours = parseField("timezone hours", s, TIME_SEP, 2, 2, strict);
      if (strict && tzHours > 14) throw new DateFormatException("timezone offset hours out of range [0..14]", s.str, s.index - 2);
      
      int tzMin = parseField("timezone minutes", s, null, 2, 2, strict);
      if (strict && tzMin > 59) throw new DateFormatException("timezone offset hours out of range [0..59]", s.str, s.index - 1);
      if (strict && tzHours == 14 && tzMin > 0) throw new DateFormatException("timezone offset may not be greater than 14 hours", s.str, s.index - 1);
      
      return sign * (tzHours * MINS_IN_HOUR + tzMin) * MS_IN_MIN;
    }
    // Reached the end of input with no timezone specified.
    return null;
  }
  
  /** Parse a field from input, validating its delimiter and length if requested. */
  private static int parseField(String field, Input s, Character delim, int minLen, int maxLen, boolean strict) {
    int startIndex = s.index;
    int result = parseInt(s);
    if (startIndex == s.index) throw new DateFormatException("missing value for field '" + field + "'", s.str, startIndex);
    if (strict) {
      int len = s.index - startIndex;
      if (len < minLen) throw new DateFormatException("field '" + field + "' must be at least " + minLen + " digits wide", s.str, startIndex);
      if (maxLen > 0 && len > maxLen) throw new DateFormatException("field '" + field + "' must be no more than " + minLen + " digits wide", s.str, startIndex);
    }
    if (delim != null) {
      if (s.index >= s.len) throw new DateFormatException("unexpected end of input", s.str, s.index);
      if (strict && s.getChar() != delim.charValue()) throw new DateFormatException("unexpected character, expected '" + delim + "'", s.str, s.index);
      s.index++;
    }
    return result;
  }
  
  /** Parse an integer from the input, reading up to the first non-numeric character. */
  private static int parseInt(Input s) {
    if (s.index >= s.len) throw new DateFormatException("unexpected end of input", s.str, s.index);
    int result = 0;
    while (s.index < s.len) {
      char c = s.getChar();
      if (c >= '0' && c <= '9') {
        if (result >= Integer.MAX_VALUE / 10) throw new ArithmeticException("Field too large.");
        result = result * 10 + ((int)c - (int)'0');
        s.index++;
      } else {
        break;
      }
    }
    return result;
  }
  
  /** Class to wrap the input string so that its index may be advanced by helper methods. */
  private static class Input {
    int index = 0;
    final String str;
    final int len;
    Input(String s) {
      this.str = s;
      this.len = s.length();
    }
    char getChar() { return str.charAt(index); }
  }
  
}
spark-api/src/main/java/spark/api/Connection.java
public interface Connection extends Closeable {

  /**
   * Is this connection closed?
   * @return True if closed
   */
  boolean isClosed();
  
  /**
   * Get the DataSource which created this connection.
   * @return Originating DataSource
   */  
  DataSource getDataSource();
  
  /**
   * Get the service description metadata for this connection.
   * @return The service description metadata
   */
  ServiceDescription getDescription();
  
  /**
   * Create a command for executing SPARQL statements.
   * @param commandString The SPARQL statement
   * @return The command
   */
  Command createCommand(String commandString);
  
}
spark-api/src/main/java/spark/api/rdf/RDFNode.java
public interface RDFNode {

}
spark-api/src/main/java/spark/api/rdf/Literal.java
public interface Literal extends RDFNode {
  
  /**
   * The lexical representation of the literal.
   * @return The lexical representation, should never be null
   */
  String getLexical();
  
}
spark-spi/src/main/java/spark/spi/rdf/TypedLiteralImpl.java
public class TypedLiteralImpl implements TypedLiteral {

  private final String lexical;
  private final URI dataType;
  
  /**
   * Construct a TypedLiteralImpl with a lexical representation and dataType, 
   * both required.
   * @param lexical Lexical representation, must be non-null
   * @param dataType XSD data type, must be non-null
   * @throws NullPointerException If parameters are null
   */
  public TypedLiteralImpl(String lexical, URI dataType) {
    if(lexical == null) {
      throw new NullPointerException("Plain literals must have non-null lexical values.");
    }
    if(dataType == null) {
      throw new NullPointerException("Plain literals must have non-null data types.");
    }

    this.lexical = lexical;
    this.dataType = dataType;
  }
  
  @Override
  public URI getDataType() {
    return this.dataType;
  }

  @Override
  public String getLexical() {
    return this.lexical;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((dataType == null) ? 0 : dataType.hashCode());
    result = prime * result + ((lexical == null) ? 0 : lexical.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof TypedLiteral))
      return false;
    TypedLiteral other = (TypedLiteral) obj;
    if (dataType == null) {
      if (other.getDataType() != null)
        return false;
    } else if (!dataType.equals(other.getDataType()))
      return false;
    if (lexical == null) {
      if (other.getLexical() != null)
        return false;
    } else if (!lexical.equals(other.getLexical()))
      return false;
    return true;
  }

  @Override
  public String toString() {
    return "\"" + lexical + "\"^^<" + dataType.toString() + ">";
  }
}
spark-api/src/main/java/spark/api/Command.java
public interface Command extends Closeable {

  /**
   * Is the command closed?
   * @return True if closed
   */
  boolean isClosed();
  
  /**
   * Get the connection from whence this came.
   * @return The source connection
   */
  Connection getConnection();
  
  /**
   * Get the command string this command will execute.
   * @return The command string
   */
  String getCommand();

  // Timeouts and cancellations

  /**
   * Constant specifying no timeout, for use with {@link #setTimeout(long)}
   */
  public static final long NO_TIMEOUT = 0L;
  
  /**
   * Set the command timeout in seconds or {@link #NO_TIMEOUT} for none.
   * @param seconds Timeout in seconds
   */
  void setTimeout(long seconds);
  
  /**
   * Get the command timeout in seconds.
   * @return Timeout in seconds
   */
  long getTimeout();
  
  /**
   * Cancel execution of this command.
   */
  void cancel();
  
  // Preparation and batching 
  
  /**
   * Add parameter bindings prior to the execution of this command.  The Map
   * will contain variable names as keys and rdf data as values.
   * @param binding A set of bindings from variable to value
   */
  void addParameterBindings(Map<String, RDFNode> binding);
  
  /**
   * Get all parameter bindings that have been added.
   * @return A series of parameter bindings from variable to value
   */
  List<Map<String, RDFNode>> getBindings();
  
  /**
   * Clear all parameter bindings
   */
  void clearBindings();
    
  // Execute 
  
  /**
   * Execute the command and return the natural result style depending on the command.
   * @return The result of execution
   */
  Result execute();
  
  /**
   * Execute a SELECT query and return a Solutions result.
   * @return The result solutions
   */
  Solutions executeQuery();
  
  /**
   * Execute a CONSTRUCT or DESCRIBE query and return a Triples result.
   * @return The result triples.
   */
  Triples executeGraph();
  
  /**
   * Execute an ASK query and return either true or false.
   * @return The result of the ASK
   */
  boolean executeAsk();
}
spark-spi/src/main/java/spark/spi/rdf/PlainLiteralImpl.java
public class PlainLiteralImpl implements PlainLiteral {

  private final String lexical;
  private final String language;

  /**
   * Construct a PlainLiteralImpl with a lexical representation and an optional language tag
   * @param lexical Lexical representation, cannot be null
   * @param language Language tag, may be null
   * @throws NullPointerException If lexical is null
   */
  public PlainLiteralImpl(String lexical, String language) {
    if(lexical == null) {
      throw new NullPointerException("Plain literals must have non-null lexical values.");
    }
    this.lexical = lexical;
    this.language = language;
  }
  
  /**
   * Construct a PlainLiteralImpl with a lexical representation but no language tag.
   * @param lexical Lexial representation, required
   * @throws NullPointerException If lexical is null
   */
  public PlainLiteralImpl(String lexical) {
    this(lexical, null);
  }
  
  @Override
  public String getLanguage() {
    return this.language;
  }

  @Override
  public String getLexical() {
    return this.lexical;
  }
  
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((language == null) ? 0 : language.hashCode());
    result = prime * result + ((lexical == null) ? 0 : lexical.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof PlainLiteral))
      return false;
    PlainLiteral other = (PlainLiteral) obj;
    if (language == null) {
      if (other.getLanguage() != null)
        return false;
    } else if (!language.equals(other.getLanguage()))
      return false;
    if (lexical == null) {
      if (other.getLexical() != null)
        return false;
    } else if (!lexical.equals(other.getLexical()))
      return false;
    return true;
  }

  @Override
  public String toString() {
    return "\"" + this.lexical + "\"" + (language != null ? "@" + language : "");
  }
}
sherpa-java/src/test/java/sherpa/server/DummyQueryResponder.java
public class DummyQueryResponder implements SherpaServer {

  private static final Logger logger = LoggerFactory.getLogger(DummyQueryResponder.class);
  
  public static final int DEFAULT_WIDTH = 2;
  
  public final int rows;
  public final int width;
  public final List<List<Object>> data;
  public List<String> messages = new ArrayList<String>();
  
  /** Create a dummy responder with generated data. */
  public DummyQueryResponder(int rows) {
    this.rows = rows;
    this.width = DEFAULT_WIDTH;
    this.data = new ArrayList<List<Object>>(rows);
    for (int row = 1; row <= rows; row++) {
      IRI iri = new IRI();
      iri.iri = "http://foobar.baz/this/uri/" + row;
      data.add(Arrays.<Object>asList(iri, row));
    }
  }
  
  /** Create a dummy responder with specified test data. */
  public DummyQueryResponder(List<List<Object>> data) {
    this.data = data;
    this.rows = (data != null) ? data.size() : 0;
    this.width = (rows > 0) ? data.get(0).size() : 0; // Assume all rows are the same width.
  }
  
  public void record(Object... pairs) {
    StringBuilder str = new StringBuilder();
    int i = 0;
    while(i < pairs.length) {
      str.append(pairs[i++] + "=" + pairs[i++] + " ");
    }
    messages.add(str.toString());
  }
  
  @Override
  public QueryResponse query(QueryRequest query) throws AvroRemoteException,
      ErrorResponse {
  
    logger.debug("Server got query request for {}", query.sparql);
    
    QueryResponse response = new QueryResponse();
    response.queryId = "1";
    response.vars = new ArrayList<CharSequence>(width);
    for (int i = 0; i < width; i++) {
      response.vars.add(Character.toString((char)('a' + i)));
    }

    logger.debug("Server sending query response");
   
    record("Message", "query", "sparql", query.sparql, "params", query.parameters, "props", query.properties);
    return response;
  }

  private List<List<Object>> batch(int begin, int size) {
    return data.subList(begin - 1, begin + size - 1);
  }
  
  @Override
  public DataResponse data(DataRequest dataRequest) throws AvroRemoteException,
      ErrorResponse {
    
    logger.debug("Server got data request for {}", dataRequest.startRow);
    record("Message", "data", "queryId", dataRequest.queryId, 
        "startRow", dataRequest.startRow, 
        "maxSize", dataRequest.maxSize);
    
    if(rows == 0) {
      DataResponse response = new DataResponse();
      response.queryId = dataRequest.queryId;
      response.startRow = 1;
      response.more = false;
      response.data = Collections.emptyList();
      logger.debug("Server sending empty response for 0 row result.");
      return response;
      
    } else if(dataRequest.startRow <= rows) {
      DataResponse response = new DataResponse();
      response.queryId = dataRequest.queryId;
      response.startRow = dataRequest.startRow;
      
      int size = dataRequest.maxSize;
      int last = dataRequest.startRow + size - 1;   // 1-based
      if(last > rows) {
        size = rows - dataRequest.startRow + 1;
      }
      response.data = batch(dataRequest.startRow, size);
      response.more = (response.startRow + size - 1) < rows;
      
      logger.debug("Server sending response for {}..{}",
          dataRequest.startRow, (dataRequest.startRow+response.data.size()-1));
      return response;
      
    } else {
      ErrorResponse response = new ErrorResponse();
      response.code = ReasonCode.Error;
      response.serverException = new ServerException();
      response.serverException.message = "Invalid request for rows outside the result set.";
      throw response;
    }
  }

  @Override
  public CloseResponse cancel(CancelRequest cancel) throws AvroRemoteException,
      ErrorResponse {
    
    record("Message", "cancel", "queryId", cancel.queryId);

    CloseResponse response = new CloseResponse();
    response.queryId = cancel.queryId;
    return response;
  }

  @Override
  public CloseResponse close(CloseRequest close) throws AvroRemoteException,
      ErrorResponse {

    record("Message", "close", "queryId", close.queryId);

    CloseResponse response = new CloseResponse();
    response.queryId = close.queryId;
    return response;
  }

}
sherpa-java/src/test/java/sherpa/server/DummySherpaServer.java
public class DummySherpaServer extends SpecificResponder {

  private static final Logger logger = LoggerFactory.getLogger(DummySherpaServer.class);
  
  private final Server server;
  
  public DummySherpaServer(int rows) {
    this(new DummyQueryResponder(rows));
  }
  
  public DummySherpaServer(List<List<Object>> data) {
    this(new DummyQueryResponder(data));
  }
  
  public DummySherpaServer(SherpaServer responder) {
    super(SherpaServer.class, responder);

    try {
      server = new SaslSocketServer(this, new InetSocketAddress((InetAddress)null, 0));
      server.start();
    } catch(IOException e) {
      throw new SparqlException("Error starting server.");
    }
  }
  
  public InetSocketAddress getAddress() {
    return new InetSocketAddress((InetAddress)null, this.server.getPort());
  }
  
  public void shutdown() {
    server.close();
  }
  
  public static void main(String args[]) throws Exception {
    int rows = 100;
    if(args.length > 0) {
      rows = Integer.parseInt(args[0]);
    }
    DummySherpaServer s = new DummySherpaServer(rows);
    InetSocketAddress address = s.getAddress();
    logger.info("Started server at: {}:{}", address.getHostName(), address.getPort());    
    Thread.currentThread().join();
  }
}
spark-api/src/main/java/spark/api/DataSource.java
public interface DataSource {

  /**
   * Create (or prepare) a Connection using the specified Credentials.
   * 
   * @param creds The connections credentials (user, password, certificate, etc)
   * @return The connection
   */
  Connection getConnection(Credentials creds);
  
  /**
   * Close the DataSource, releasing any underlying resources. Users should
   * close data sources when they are finished working with them, and once
   * closed a data source should not be used any further.
   */
  void close();
  
}
spark-spi/src/test/java/spark/spi/TestCursor.java
public static final int AFTER_LAST = 8;
spark-spi/src/main/java/spark/spi/rdf/BlankNodeImpl.java
public class BlankNodeImpl implements BlankNode {

  private final String label;
  
  /**
   * Construct a new BlankNode with a label
   * @param label
   */
  public BlankNodeImpl(String label) {
    this.label = label;
  }
  
  @Override
  public String getLabel() {
    return this.label;
  }

  
  
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((label == null) ? 0 : label.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof BlankNode))
      return false;
    BlankNode other = (BlankNode) obj;
    if (label == null) {
      if (other.getLabel() != null)
        return false;
    } else if (!label.equals(other.getLabel()))
      return false;
    return true;
  }

  public String toString() {
    return "_:" + label;
  }
}
spark-spi/src/test/java/spark/spi/TestCursor.java
public static final int LAST = 4;
spark-spi/src/test/java/spark/spi/TestCursor.java
public static final int BEFORE_FIRST = 1;
spark-spi/src/main/java/spark/spi/rdf/NamedNodeImpl.java
public class NamedNodeImpl implements NamedNode {

  private final URI uri;
  
  /**
   * Construct a new NamedNodeImpl with a URI.
   * @param uri URI
   */
  public NamedNodeImpl(URI uri) {
    if(uri == null) {
      throw new NullPointerException();
    }
    this.uri = uri;
  }
  
  @Override
  public URI getURI() {
    return this.uri;
  }

  @Override
  public int hashCode() {
    return uri.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj instanceof NamedNode) {
      return this.uri.equals(((NamedNode)obj).getURI());
    } else {
      return false;
    }
  }
  
  @Override
  public String toString() {
    return "<" + uri.toString() + ">";
  }
}
spark-api/src/main/java/spark/api/credentials/NoCredentials.java
public final class NoCredentials implements Credentials {

  /**
   * The shared stateless instance.
   */
  public static NoCredentials INSTANCE = new NoCredentials();
  
  private NoCredentials() {}
  
  @Override
  public int hashCode() { 
    return 0;
  }

  @Override
  public boolean equals(Object obj) {
    return obj instanceof NoCredentials;
  }  
}
spark-api/src/main/java/spark/api/rdf/NamedNode.java
public interface NamedNode extends Resource {

  /**
   * Gets the URI for the named resource
   */
  URI getURI();

}
spark-spi/src/test/java/spark/spi/TestCursor.java
public static final int FIRST = 2;
spark-api/src/main/java/spark/api/Solutions.java
public interface Solutions extends CursoredResult<Map<String,RDFNode>> {
  
  /**
   * Get all variables possible in these solutions.
   * @return List of variable names
   */
  List<String> getVariables();
  
  // Generic data access as map or node from the current solution
  
  /**
   * Check whether the specified variable is bound in the 
   * current solution.
   * @param variable Variable name 
   * @return True if bound 
   */
  boolean isBound(String variable);
  
  /**
   * Get the bound value for the specified variable or null
   * if unbound. 
   * @param variable The variable name
   * @return The value or null if unbound
   */
  RDFNode getBinding(String variable);
    
  // Generic node access, may fail if var is wrong type
  
  /**
   * Get the value of a variable as a named node (IRI).  If 
   * the value is not a named node, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The named node value for this variable or null if unbound 
   * @throws SparqlException If the value is not a named node
   */
  NamedNode getNamedNode(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a blank node.  If the value 
   * is not a blank node, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The blank node value for this variable or null if unbound
   * @throws SparqlException If the value is not a blank node
   */  
  BlankNode getBlankNode(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a literal.  If the value 
   * is not a literal, then a SparqlException is thrown. 
   * @param variable The variable name
   * @return The literal value for this variable or null if unbound
   * @throws SparqlException If the value is not a literal
   */
  Literal getLiteral(String variable) throws SparqlException;
  
  // Literal value mappings based on XSD, may fail if var is wrong type
  
  /**
   * Get the value of a variable as a URI.  If the value is not a named 
   * node, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The URI for this named node resource or null if unbound
   * @throws SparqlException If the value is not a named node
   */
  URI getURI(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a string.  If the value is not a plain
   * literal or an xsd:string typed literal, then a SparqlException is 
   * thrown.
   * @param variable The variable name
   * @return The string for this plain literal or typed xsd:string literal
   * @throws SparqlException If the value is not a plain literal or xsd:string typed literal
   */
  String getString(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as an int.  If the value is not
   * an xsd:int typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The int for this typed xsd:int literal
   * @throws SparqlException If the value is not an xsd:int literal
   */  
  int getInt(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a boolean.  If the value is not
   * an xsd:boolean typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The boolean for this typed xsd:boolean literal
   * @throws SparqlException If the value is not an xsd:boolean literal
   */  
  boolean getBoolean(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a float.  If the value is not
   * an xsd:float typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The float for this typed xsd:float literal
   * @throws SparqlException If the value is not an xsd:float literal
   */  
  float getFloat(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a double.  If the value is not
   * an xsd:double typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The long for this typed xsd:double literal
   * @throws SparqlException If the value is not an xsd:double literal
   */  
  double getDouble(String variable) throws SparqlException;
  
  /**
   * Get the value of a variable as a BigInteger.  If the value is not 
   * an xsd:integer typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The BigInteger for this typed xsd:integer literal
   * @throws SparqlException If the value is not an xsd:integer literal
   */  
  BigInteger getInteger(String variable) throws SparqlException;

  /**
   * Get the value of a variable as a Date.  If the value is not a 
   * an xsd:dateTime typed literal, then a SparqlException is thrown.
   * @param variable The variable name
   * @return The Date for this typed xsd:dateTime literal
   * @throws SparqlException If the value is not an xsd:dateTime literal
   */  
  Date getDateTime(String variable) throws SparqlException;  
}
spark-spi/src/test/java/spark/spi/TestCursor.java
public static final int NONE = 0;
spark-api/src/main/java/spark/api/uris/XsdTypes.java
public interface XsdTypes {

  /**
   * XSD base namespace = http://www.w3.org/2001/XMLSchema#
   */
  public static final String XSD_BASE = "http://www.w3.org/2001/XMLSchema#";
  
  /**
   * http://www.w3.org/2001/XMLSchema#anyType
   */
  public static final URI ANY_TYPE = URI.create(XSD_BASE + "anyType");

  /**
   * http://www.w3.org/2001/XMLSchema#anySimpleType
   */
  public static final URI ANY_SIMPLE_TYPE = URI.create(XSD_BASE + "anySimpleType");
  
  /**
   * http://www.w3.org/2001/XMLSchema#anyAtomicType
   */
  public static final URI ANY_ATOMIC_TYPE = URI.create(XSD_BASE + "anyAtomicType");

  /**
   * http://www.w3.org/2001/XMLSchema#duration
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>: "xsd:duration
   * does not have a well-defined value space."
   */
  @Deprecated
  public static final URI DURATION = URI.create(XSD_BASE + "duration");

  /**
   * http://www.w3.org/2001/XMLSchema#dayTimeDuration
   */
  public static final URI DAY_TIME_DURATION = URI.create(XSD_BASE + "dayTimeDuration");

  /**
   * http://www.w3.org/2001/XMLSchema#yearMonthDuration
   */
  public static final URI YEAR_MONTH_DURATION = URI.create(XSD_BASE + "yearMonthDuration");

  /**
   * http://www.w3.org/2001/XMLSchema#dateTime
   */
  public static final URI DATE_TIME = URI.create(XSD_BASE + "dateTime");

  /**
   * http://www.w3.org/2001/XMLSchema#dateTimeStamp
   */
  public static final URI DATE_TIME_STAMP = URI.create(XSD_BASE + "dateTimeStamp");

  /**
   * http://www.w3.org/2001/XMLSchema#time
   */
  public static final URI TIME = URI.create(XSD_BASE + "time");

  /**
   * http://www.w3.org/2001/XMLSchema#date
   */
  public static final URI DATE = URI.create(XSD_BASE + "date");

  /**
   * http://www.w3.org/2001/XMLSchema#gYearMonth
   */
  public static final URI G_YEAR_MONTH = URI.create(XSD_BASE + "gYearMonth");

  /**
   * http://www.w3.org/2001/XMLSchema#gYear
   */
  public static final URI G_YEAR = URI.create(XSD_BASE + "gYear");

  /**
   * http://www.w3.org/2001/XMLSchema#gMonthDay
   */
  public static final URI G_MONTH_DAY = URI.create(XSD_BASE + "gMonthDay");

  /**
   * http://www.w3.org/2001/XMLSchema#gDay
   */
  public static final URI G_DAY = URI.create(XSD_BASE + "gDay");

  /**
   * http://www.w3.org/2001/XMLSchema#gMonth
   */
  public static final URI G_MONTH = URI.create(XSD_BASE + "gMonth");

  /**
   * http://www.w3.org/2001/XMLSchema#boolean
   */
  public static final URI BOOLEAN = URI.create(XSD_BASE + "boolean");

  /**
   * http://www.w3.org/2001/XMLSchema#base64Binary
   */
  public static final URI BASE_64_BINARY = URI.create(XSD_BASE + "base64Binary");

  /**
   * http://www.w3.org/2001/XMLSchema#hexBinary
   */
  public static final URI HEX_BINARY = URI.create(XSD_BASE + "hexBinary");

  /**
   * http://www.w3.org/2001/XMLSchema#float
   */
  public static final URI FLOAT = URI.create(XSD_BASE + "float");

  /**
   * http://www.w3.org/2001/XMLSchema#double
   */
  public static final URI DOUBLE = URI.create(XSD_BASE + "double");

  /**
   * http://www.w3.org/2001/XMLSchema#anyURI
   */
  public static final URI ANY_URI = URI.create(XSD_BASE + "anyURI");

  /**
   * http://www.w3.org/2001/XMLSchema#QName
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>: "requires
   * an enclosing XML document context."
   */
  @Deprecated
  public static final URI QNAME = URI.create(XSD_BASE + "QName");

  /**
   * http://www.w3.org/2001/XMLSchema#NOTATION
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>:
   * "xsd:NOTATION is not intended for direct use."
   */
  @Deprecated
  public static final URI NOTATION = URI.create(XSD_BASE + "NOTATION");

  /**
   * http://www.w3.org/2001/XMLSchema#ID
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>: "for cross
   * references within an XML document."
   */
  @Deprecated
  public static final URI ID = URI.create(XSD_BASE + "ID");

  /**
   * http://www.w3.org/2001/XMLSchema#IDREF
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>: "for cross
   * references within an XML document."
   */
  @Deprecated
  public static final URI IDREF = URI.create(XSD_BASE + "IDREF");

  /**
   * http://www.w3.org/2001/XMLSchema#IDREFS
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>:
   * "sequence-valued datatype which does not fir the RDF datatype model"
   */
  @Deprecated
  public static final URI IDREFS = URI.create(XSD_BASE + "IDREFS");

  /**
   * http://www.w3.org/2001/XMLSchema#ENTITY
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>: "requires
   * an enclosing XML document context."
   */
  @Deprecated
  public static final URI ENTITY = URI.create(XSD_BASE + "ENTITY");

  /**
   * http://www.w3.org/2001/XMLSchema#ENTITIES
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>:
   * "sequence-valued datatype which does not fir the RDF datatype model"
   */
  @Deprecated
  public static final URI ENTITIES = URI.create(XSD_BASE + "ENTITIES");

  /**
   * http://www.w3.org/2001/XMLSchema#normalizedString
   */
  public static final URI NORMALIZED_STRING = URI.create(XSD_BASE + "normalizedString");

  /**
   * http://www.w3.org/2001/XMLSchema#token
   */
  public static final URI TOKEN = URI.create(XSD_BASE + "token");

  /**
   * http://www.w3.org/2001/XMLSchema#language
   */
  public static final URI LANGUAGE = URI.create(XSD_BASE + "language");

  /**
   * http://www.w3.org/2001/XMLSchema#NMTOKEN
   */
  public static final URI NMTOKEN = URI.create(XSD_BASE + "NMTOKEN");

  /**
   * http://www.w3.org/2001/XMLSchema#NMTOKENS
   * @deprecated per <a href="http://www.w3.org/TR/rdf-mt/#dtype_interp">RDF Semantics</a>:
   * "sequence-valued datatype which does not fir the RDF datatype model"
   */
  @Deprecated
  public static final URI NMTOKENS = URI.create(XSD_BASE + "NMTOKENS");

  /**
   * http://www.w3.org/2001/XMLSchema#Name
   */
  public static final URI NAME = URI.create(XSD_BASE + "Name");

  /**
   * http://www.w3.org/2001/XMLSchema#NCName
   */
  public static final URI NC_NAME = URI.create(XSD_BASE + "NCName");

  /**
   * http://www.w3.org/2001/XMLSchema#string
   */
  public static final URI STRING = URI.create(XSD_BASE + "string");

  /**
   * http://www.w3.org/2001/XMLSchema#decimal
   */
  public static final URI DECIMAL = URI.create(XSD_BASE + "decimal");

  /**
   * http://www.w3.org/2001/XMLSchema#precisionDecimal
   */
  public static final URI PRECISION_DECIMAL = URI.create(XSD_BASE + "precisionDecimal");

  /**
   * http://www.w3.org/2001/XMLSchema#integer
   */
  public static final URI INTEGER = URI.create(XSD_BASE + "integer");

  /**
   * http://www.w3.org/2001/XMLSchema#nonPositiveInteger
   */
  public static final URI NON_POSITIVE_INTEGER = URI.create(XSD_BASE + "nonPositiveInteger");

  /**
   * http://www.w3.org/2001/XMLSchema#negativeInteger
   */
  public static final URI NEGATIVE_INTEGER = URI.create(XSD_BASE + "negativeInteger");
  
  /**
   * http://www.w3.org/2001/XMLSchema#long
   */
  public static final URI LONG = URI.create(XSD_BASE + "long");

  /**
   * http://www.w3.org/2001/XMLSchema#int
   */
  public static final URI INT = URI.create(XSD_BASE + "int");

  /**
   * http://www.w3.org/2001/XMLSchema#short
   */
  public static final URI SHORT = URI.create(XSD_BASE + "short");

  /**
   * http://www.w3.org/2001/XMLSchema#byte
   */
  public static final URI BYTE = URI.create(XSD_BASE + "byte");

  /**
   * http://www.w3.org/2001/XMLSchema#nonNegativeInteger
   */
  public static final URI NON_NEGATIVE_INTEGER = URI.create(XSD_BASE + "nonNegativeInteger");

  /**
   * http://www.w3.org/2001/XMLSchema#unsignedLong
   */
  public static final URI UNSIGNED_LONG = URI.create(XSD_BASE + "unsignedLong");

  /**
   * http://www.w3.org/2001/XMLSchema#unsignedInt
   */
  public static final URI UNSIGNED_INT = URI.create(XSD_BASE + "unsignedInt");

  /**
   * http://www.w3.org/2001/XMLSchema#unsignedShort
   */
  public static final URI UNSIGNED_SHORT = URI.create(XSD_BASE + "unsignedShort");

  /**
   * http://www.w3.org/2001/XMLSchema#unsignedByte
   */
  public static final URI UNSIGNED_BYTE = URI.create(XSD_BASE + "unsignedByte");

  /**
   * http://www.w3.org/2001/XMLSchema#positiveInteger
   */
  public static final URI POSITIVE_INTEGER = URI.create(XSD_BASE + "positiveInteger");
}
spark-spi/src/test/java/spark/spi/TestCursor.java
public class TestCursor {

  /** Return an empty test suite so JUnit doesn't complain. */
  public static Test suite() {
    return new TestSuite();
  }
  
  /** State when none of <tt>isBeforeFirst</tt>, <tt>isFirst</tt>, <tt>isLast</tt>, or <tt>isAfterLast</tt> is true. */
  public static final int NONE = 0;
  
  /** Flag to indicate that {@link CursoredResult#isBeforeFirst()} should be true. */
  public static final int BEFORE_FIRST = 1;
  
  /** Flag to indicate that {@link CursoredResult#isFirst()} should be true. */
  public static final int FIRST = 2;
  
  /** Flag to indicate that {@link CursoredResult#isLast()} should be true. */
  public static final int LAST = 4;
  
  /** Flag to indicate that {@link CursoredResult#isAfterLast()} should be true. */
  public static final int AFTER_LAST = 8;
  
  /**
   * Asserts that the current state of the result object matches the given state.
   * @param result The result.
   * @param state The expected state; a bitwise-or combination of the appropriate flags
   * ({@link #BEFORE_FIRST}, {@link #FIRST}, {@link #LAST}, and {@link #AFTER_LAST}), or {@link #NONE}.
   * The absence of a flag is taken to mean that the corresponding method should return false.
   */
  public static void assertCursor(CursoredResult<?> result, int state) {
    Assert.assertEquals((state & BEFORE_FIRST) > 0, result.isBeforeFirst());
    Assert.assertEquals((state & FIRST) > 0, result.isFirst());
    Assert.assertEquals((state & LAST) > 0, result.isLast());
    Assert.assertEquals((state & AFTER_LAST) > 0, result.isAfterLast());
  }
}


Question: import static spark.spi.TestCursor.AFTER_LAST;
import static spark.spi.TestCursor.BEFORE_FIRST;
import static spark.spi.TestCursor.FIRST;
import static spark.spi.TestCursor.LAST;
import static spark.spi.TestCursor.NONE;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.apache.avro.AvroRemoteException;
import org.apache.avro.util.Utf8;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sherpa.protocol.BNode;
import sherpa.protocol.IRI;
import sherpa.protocol.PlainLiteral;
import sherpa.protocol.QueryRequest;
import sherpa.protocol.QueryResponse;
import sherpa.protocol.TypedLiteral;
import sherpa.server.DummyQueryResponder;
import sherpa.server.DummySherpaServer;
import spark.api.Command;
import spark.api.Connection;
import spark.api.DataSource;
import spark.api.Solutions;
import spark.api.credentials.NoCredentials;
import spark.api.rdf.Literal;
import spark.api.rdf.NamedNode;
import spark.api.rdf.RDFNode;
import spark.api.uris.XsdTypes;
import spark.spi.TestCursor;
import spark.spi.rdf.BlankNodeImpl;
import spark.spi.rdf.NamedNodeImpl;
import spark.spi.rdf.PlainLiteralImpl;
import spark.spi.rdf.TypedLiteralImpl;
import spark.spi.util.DateTime;
      }

      TestCursor.assertCursor(solutions, AFTER_LAST);
      
      logger.info("Read {} rows", counter);
      Assert.assertEquals(resultRows, counter);

    } finally {
      server.shutdown();
    }
  }
  
  public void helpCheckRows(List<Map<String,RDFNode>> data, int size) {
    Assert.assertEquals(size, data.size());
    for (Map<String,RDFNode> s : data) {
      Assert.assertNotNull(s);
    }
    for (int i = 0; i < size - 1; i++) {
      Assert.assertFalse(data.get(i).equals(data.get(i + 1)));
    }
  }

  public void helpTestIteratorNormal(int rows, int batchSize) {
    DummySherpaServer server = new DummySherpaServer(rows);
    try {
      Solutions solutions = helpExecuteQuery(server, batchSize);
      Iterator<Map<String,RDFNode>> iter = solutions.iterator();
      Assert.assertNotNull(iter);
      List<Map<String,RDFNode>> data = new ArrayList<Map<String,RDFNode>>(rows);
      // Traverse the iterator in a normal fashion.
      while (iter.hasNext()) {
        data.add(iter.next());
      }
      helpCheckRows(data, rows);
    } finally {
      server.shutdown();
    }
  }
  
  public void helpTestIteratorParanoid(int rows, int batchSize) {
    DummySherpaServer server = new DummySherpaServer(rows);
    try {
      Solutions solutions = helpExecuteQuery(server, batchSize);
      Iterator<Map<String,RDFNode>> iter = solutions.iterator();
      Assert.assertNotNull(iter);
      List<Map<String,RDFNode>> data = new ArrayList<Map<String,RDFNode>>(rows);
      // Traverse the iterator, with lots of extra checks to hasNext();
      Assert.assertEquals(rows > 0, iter.hasNext());
      Assert.assertEquals(rows > 0, iter.hasNext());
      while (iter.hasNext()) {
        data.add(iter.next());
        Assert.assertEquals(data.size() < rows, iter.hasNext());
      }
      helpCheckRows(data, rows);
    } finally {
      server.shutdown();
    }
  }
  
  public void helpTestIteratorCount(int rows, int batchSize) {
    DummySherpaServer server = new DummySherpaServer(rows);
    try {
      Solutions solutions = helpExecuteQuery(server, batchSize);
      Iterator<Map<String,RDFNode>> iter = solutions.iterator();
      Assert.assertNotNull(iter);
      List<Map<String,RDFNode>> data = new ArrayList<Map<String,RDFNode>>(rows);
      // Traverse the iterator without calling hasNext()
      for (int i = 0; i < rows; i++) {
        data.add(iter.next());
      }
      Assert.assertFalse(iter.hasNext());
      helpCheckRows(data, rows);
    } finally {
      server.shutdown();
    }
  }
  
  @Test
  public void testCursor() {
    helpTestQueryCursor(0, 10);
    helpTestQueryCursor(1, 10);
    helpTestQueryCursor(9, 10);
    helpTestQueryCursor(10, 10);
    helpTestQueryCursor(11, 10);
    helpTestQueryCursor(20, 10);
  }

  @SuppressWarnings("unchecked")
  @Test
  public void testTimeoutPassedDown() {
    final List<Object> results = new ArrayList<Object>();    
    DummySherpaServer server = new DummySherpaServer(
        new DummyQueryResponder(10) {
          public QueryResponse query(QueryRequest query)
              throws AvroRemoteException {
            results.add(query.properties);
            return super.query(query);
          }
        });
    InetSocketAddress serverAddress = server.getAddress();
    
    try {
      DataSource ds = new SHPDataSource(serverAddress.getHostName(), serverAddress.getPort());
      Connection conn = ds.getConnection(NoCredentials.INSTANCE);
      Command command = conn.createCommand("SELECT ?x ?y WHERE { this should be a real query but the test doesn't actually do anything real. }");
      command.setTimeout(1234);
      command.executeQuery();
      
      // Kind of tricky here - the keys and values are now Avro Utf8 instances which don't compare equal to Strings
      Map<CharSequence,CharSequence> serverProps = (Map<CharSequence,CharSequence>)results.get(0);
      Assert.assertEquals(new Utf8("1234"), serverProps.get(new Utf8(QueryExecution.TIMEOUT)));
      
    } finally {
      server.shutdown();
    }

  }
  
  @Test
  public void testData() {
Answer:     NamedNodeImpl uri1 = new NamedNodeImpl(URI.create("http://example.org/foo"));