/**
   * {@inheritDoc}
   *
   * @see com.continuent.tungsten.replicator.storage.Store#status()
   */
  @Override
  public TungstenProperties status() {
    TungstenProperties props = new TungstenProperties();
    props.setLong(Replicator.MIN_STORED_SEQNO, getMinStoredSeqno());
    props.setLong(Replicator.MAX_STORED_SEQNO, getMaxStoredSeqno());
    props.setLong("activeSeqno", diskLog.getActiveSeqno());
    props.setBoolean("doChecksum", doChecksum);
    props.setString("logDir", logDir);
    props.setInt("logFileSize", logFileSize);
    props.setLong("logFileRetainMillis", logFileRetainMillis);
    props.setLong("logFileSize", diskLog.getLogFileSize());
    props.setLong("timeoutMillis", diskLog.getTimeoutMillis());
    props.setBoolean("fsyncOnFlush", fsyncOnFlush);
    props.setLong("flushIntervalMillis", diskLog.getFlushIntervalMillis());
    props.setLong("timeoutMillis", diskLog.getTimeoutMillis());
    props.setLong("logConnectionTimeout", logConnectionTimeout);
    props.setBoolean("readOnly", readOnly);

    return props;
  }
  /**
   * Get the last applied event. We first try the disk log then if that is absent try the catalog.
   * If there is nothing there we must be starting from scratch and return null.
   *
   * @return An event header or null if log is newly initialized
   * @throws InterruptedException
   * @throws ReplicatorException
   */
  public ReplDBMSHeader getLastAppliedEvent() throws ReplicatorException, InterruptedException {
    // Look for maximum sequence number in log and use that if available.
    if (diskLog != null) {

      long maxSeqno = diskLog.getMaxSeqno();
      if (maxSeqno > -1) {
        LogConnection conn = null;
        try {
          // Try to connect and find the event.
          THLEvent thlEvent = null;
          conn = diskLog.connect(true);
          conn.seek(maxSeqno);
          while ((thlEvent = conn.next(false)) != null && thlEvent.getSeqno() == maxSeqno) {
            // Return only the last fragment.
            if (thlEvent.getLastFrag()) {
              ReplEvent event = thlEvent.getReplEvent();
              if (event instanceof ReplDBMSEvent) return (ReplDBMSEvent) event;
              else if (event instanceof ReplControlEvent)
                return ((ReplControlEvent) event).getHeader();
            }
          }

          // If we did not find the last fragment of the event
          // we need to warn somebody.
          if (thlEvent != null)
            logger.warn("Unable to find last fragment of event: seqno=" + maxSeqno);
        } finally {
          conn.release();
        }
      }
    }

    // If that does not work, try the catalog.
    if (catalog != null) {
      return catalog.getLastEvent();
    }

    // If we get to this point, the log is newly initialized and there is no
    // such event to return.
    return null;
  }
  /**
   * {@inheritDoc}
   *
   * @see
   *     com.continuent.tungsten.replicator.plugin.ReplicatorPlugin#release(com.continuent.tungsten.replicator.plugin.PluginContext)
   */
  public synchronized void release(PluginContext context)
      throws InterruptedException, ReplicatorException {
    // Cancel server.
    if (server != null) {
      try {
        server.stop();
      } catch (InterruptedException e) {
        logger.warn("Server stop operation was unexpectedly interrupted", e);
      } finally {
        server = null;
      }
    }

    if (catalog != null) {
      catalog.close();
      catalog = null;
    }
    if (diskLog != null) {
      diskLog.release();
      diskLog = null;
    }
  }
 /** Returns true if the indicated sequence number is available. */
 public boolean pollSeqno(long seqno) {
   return seqno <= diskLog.getMaxSeqno();
 }
 /**
  * Connect to the log. Adapters must call this to use the log.
  *
  * @param readonly If true, this is a readonly connection
  * @return A disk log client
  */
 public LogConnection connect(boolean readonly) throws ReplicatorException {
   return diskLog.connect(readonly);
 }
  /**
   * {@inheritDoc}
   *
   * @see
   *     com.continuent.tungsten.replicator.plugin.ReplicatorPlugin#prepare(com.continuent.tungsten.replicator.plugin.PluginContext)
   */
  public synchronized void prepare(PluginContext context)
      throws ReplicatorException, InterruptedException {
    // Prepare database connection.
    if (url != null && url.trim().length() > 0) {
      logger.info("Preparing SQL catalog tables");
      ReplicatorRuntime runtime = (ReplicatorRuntime) context;
      String metadataSchema = context.getReplicatorSchemaName();
      catalog = new CatalogManager(runtime);
      catalog.connect(url, user, password, metadataSchema, vendor);
      catalog.prepareSchema();
    } else logger.info("SQL catalog tables are disabled");

    // Configure and prepare the log.
    diskLog = new DiskLog();
    diskLog.setDoChecksum(doChecksum);
    diskLog.setEventSerializerClass(eventSerializer);
    diskLog.setLogDir(logDir);
    diskLog.setLogFileSize(logFileSize);
    diskLog.setLogFileRetainMillis(logFileRetainMillis);
    diskLog.setLogConnectionTimeoutMillis(logConnectionTimeout * 1000);
    diskLog.setBufferSize(bufferSize);
    diskLog.setFsyncOnFlush(fsyncOnFlush);
    if (fsyncOnFlush) {
      // Only used with fsync.
      diskLog.setFlushIntervalMillis(flushIntervalMillis);
    }
    diskLog.setReadOnly(readOnly);
    diskLog.prepare();
    logger.info("Log preparation is complete");

    // Start server for THL connections.
    if (context.isRemoteService() == false) {
      try {
        server = new Server(context, sequencer, this);
        server.start();
      } catch (IOException e) {
        throw new ReplicatorException("Unable to start THL server", e);
      }
    }
  }
 /**
  * Updates the active sequence number on the log. Log files can only be deleted if their last
  * sequence number is below this value.
  */
 public void updateActiveSeqno(long activeSeqno) {
   diskLog.setActiveSeqno(activeSeqno);
 }
 /** Return minimum stored sequence number. */
 public long getMinStoredSeqno() {
   return diskLog.getMinSeqno();
 }