package no.priv.garshol.duke.server;
import static no.priv.garshol.duke.utils.PropertyUtils.get;
import java.io.IOException;
import java.util.Properties;
import no.priv.garshol.duke.ConfigLoader;
import no.priv.garshol.duke.Configuration;
import no.priv.garshol.duke.DukeConfigException;
import no.priv.garshol.duke.DukeException;
import no.priv.garshol.duke.JDBCLinkDatabase;
import no.priv.garshol.duke.JNDILinkDatabase;
import no.priv.garshol.duke.LinkDatabase;
import no.priv.garshol.duke.Logger;
import no.priv.garshol.duke.Processor;
import no.priv.garshol.duke.RDBMSLinkDatabase;
import no.priv.garshol.duke.matchers.AbstractMatchListener;
import no.priv.garshol.duke.matchers.LinkDatabaseMatchListener;
import no.priv.garshol.duke.utils.ObjectUtils;
// we use this to make it easier to deal with properties
/**
* The central class that receives notifications from the UI and timer
* threads, controlling the actual work performed.
*/
public class DukeController extends AbstractMatchListener {
private String status; // what's up?
private int records; // number of records processed
private int batch_size; // batch size in Duke
private long lastCheck; // time we last checked
private long lastRecord; // most recent time we saw a new record
private int error_factor;// how many times to skip processing on errors
/**
* When processing fails with an error, this variable is set to some
* n, which is the number of processing() calls to skip before we
* try again. This implements longer check delays when errors occur.
*/
private int error_skips;
/**
* Size of the last batch we saw.
*/
private int last_batch_size;
private Processor processor;
private LinkDatabase linkdb;
private Logger logger;
public DukeController(Properties props) {
this.status = "Initialized, inactive";
String configfile = get(props, "duke.configfile");
try {
// setting up logger
String loggerclass = get(props, "duke.logger-class", null);
if (loggerclass != null) {
logger = (Logger) ObjectUtils.instantiate(loggerclass);
logger.debug("DukeController starting up");
}
// loading configuration
Configuration config = ConfigLoader.load(configfile);
this.processor = new Processor(config, false);
this.linkdb = makeLinkDatabase(props);
processor.addMatchListener(new LinkDatabaseMatchListener(config, linkdb));
processor.addMatchListener(this);
batch_size = get(props, "duke.batch-size", 40000);
error_factor = get(props, "duke.error-wait-skips", 6);
// add loggers
if (logger != null) {
processor.setLogger(logger);
if (linkdb instanceof RDBMSLinkDatabase)
((RDBMSLinkDatabase) linkdb).setLogger(logger);
}
} catch (Throwable e) {
// this means init failed, and we need to clean up so that we can try
// again later. unfortunately, we don't know what failed, so we need
// to be careful
if (processor != null)
try {
processor.close();
} catch (Exception e2) {
if (logger != null)
logger.error("Couldn't close processor", e2);
}
if (linkdb != null)
linkdb.close();
throw new DukeException(e); // we failed, so signal that
}
}
/**
* Runs the record linkage process.
*/
public void process() {
// are we ready to process yet, or have we had an error, and are
// waiting a bit longer in the hope that it will resolve itself?
if (error_skips > 0) {
error_skips--;
return;
}
try {
if (logger != null)
logger.debug("Starting processing");
status = "Processing";
lastCheck = System.currentTimeMillis();
// FIXME: how to break off processing if we don't want to keep going?
processor.deduplicate(batch_size);
status = "Sleeping";
if (logger != null)
logger.debug("Finished processing");
} catch (Throwable e) {
status = "Thread blocked on error: " + e;
if (logger != null)
logger.error("Error in processing; waiting", e);
error_skips = error_factor;
}
}
/**
* Shuts down the controller, releasing all resources.
*/
public void close() throws IOException {
processor.close();
linkdb.close();
}
public String getStatus() {
return status;
}
public boolean isErrorBlocked() {
return error_skips > 0 || status.startsWith("Thread blocked");
}
public long getLastCheck() {
return lastCheck;
}
public long getLastRecord() {
return lastRecord;
}
public int getRecordCount() {
return records;
}
// called by timer thread
void reportError(Throwable throwable) {
if (logger != null)
logger.error("Timer reported error", throwable);
status = "Thread blocked on error: " + throwable;
error_skips = error_factor;
}
// called by timer thread
void reportStopped() {
status = "Thread stopped";
if (logger != null)
logger.error("Timer thread has stopped");
}
// --- Listener implementation
public void batchReady(int size) {
last_batch_size = size;
}
public void batchDone() {
linkdb.commit();
records += last_batch_size;
lastRecord = System.currentTimeMillis();
}
// --- Create link database
private LinkDatabase makeLinkDatabase(Properties props) {
String dbtype = get(props, "duke.linkdbtype");
if (dbtype.equals("jdbc"))
return makeJDBCLinkDatabase(props);
else if (dbtype.equals("jndi"))
return makeJNDILinkDatabase(props);
else
throw new DukeConfigException("Unknown link database type '" + dbtype +
"'");
}
private LinkDatabase makeJDBCLinkDatabase(Properties props) {
String linkjdbcuri = get(props, "duke.linkjdbcuri");
String driverklass = get(props, "duke.jdbcdriver");
String dbtype = get(props, "duke.database");
String tblprefix = get(props, "duke.table-prefix", null);
Properties jdbcprops = new Properties();
if (get(props, "duke.username", null) != null)
jdbcprops.put("user", get(props, "duke.username"));
if (get(props, "duke.password", null) != null)
jdbcprops.put("password", get(props, "duke.password"));
JDBCLinkDatabase db;
db = new JDBCLinkDatabase(driverklass, linkjdbcuri, dbtype, jdbcprops);
if (tblprefix != null)
db.setTablePrefix(tblprefix);
db.init();
return db;
}
private LinkDatabase makeJNDILinkDatabase(Properties props) {
String tblprefix = get(props, "duke.table-prefix", null);
JNDILinkDatabase db = new JNDILinkDatabase(get(props, "duke.linkjndipath"),
get(props, "duke.database"));
if (tblprefix != null)
db.setTablePrefix(tblprefix);
db.init();
return db;
}
}