/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.server.management;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import org.apache.log4j.Logger;
import fedora.common.MalformedPIDException;
import fedora.common.PID;
import fedora.server.storage.ConnectionPool;
import fedora.server.utilities.SQLUtility;
/**
* A PIDGenerator that uses a database to keep track of the highest pid it knows
* about for each namespace.
*
* @author Chris Wilper
*/
public class DBPIDGenerator
implements PIDGenerator {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(DBPIDGenerator.class.getName());
private final HashMap m_highestID;
private PID m_lastPID;
private final ConnectionPool m_connectionPool;
/**
* Initialize the DBPIDGenerator. This initializes the memory hash with
* values in the database, if any. If oldPidGenDir is not null, the
* constructor will then call neverGeneratePID on the most recently
* generated PID as reported by the log files in that directory. This is to
* support automatic upgrade of this functionality from versions of Fedora
* prior to 1.2.
*/
public DBPIDGenerator(ConnectionPool cPool, File oldPidGenDir)
throws IOException {
m_connectionPool = cPool;
m_highestID = new HashMap();
// load the values from the database into the m_highestID hash
// pidGen: namespace highestID
Statement s = null;
ResultSet results = null;
Connection conn = null;
try {
conn = m_connectionPool.getConnection();
String query = "SELECT namespace, highestID FROM pidGen";
s = conn.createStatement();
results = s.executeQuery(query);
while (results.next()) {
m_highestID.put(results.getString("namespace"),
new Integer(results.getInt("highestID")));
}
} catch (SQLException sqle) {
LOG.warn("Unable to read pidGen table; assuming it "
+ "will be created shortly");
} finally {
try {
if (results != null) {
results.close();
}
if (s != null) {
s.close();
}
if (conn != null) {
m_connectionPool.free(conn);
}
} catch (SQLException sqle2) {
LOG.warn("Error trying to free db "
+ "resources in DBPIDGenerator", sqle2);
} finally {
results = null;
s = null;
}
}
upgradeIfNeeded(oldPidGenDir);
}
/**
* Read the highest value from the old pidGen directory if it exists, and
* ensure it is never used.
*/
private void upgradeIfNeeded(File oldPidGenDir) throws IOException {
if (oldPidGenDir != null && oldPidGenDir.isDirectory()) {
String[] names = oldPidGenDir.list();
Arrays.sort(names);
if (names.length > 0) {
BufferedReader in =
new BufferedReader(new InputStreamReader(new FileInputStream(new File(oldPidGenDir,
names[names.length - 1]))));
String lastLine = null;
String line;
while ((line = in.readLine()) != null) {
lastLine = line;
}
in.close();
if (lastLine != null) {
String[] parts = lastLine.split("|");
if (parts.length == 2) {
neverGeneratePID(parts[0]);
}
}
}
}
}
/**
* Generate a new pid that is guaranteed to be unique, within the given
* namespace.
*/
public synchronized PID generatePID(String namespace) throws IOException {
int i = getHighestID(namespace);
i++;
setHighestID(namespace, i);
try {
m_lastPID = new PID(namespace + ":" + i);
} catch (MalformedPIDException e) {
throw new IOException(e.getMessage());
}
return m_lastPID;
}
/**
* Get the last pid that was generated.
*/
public synchronized PID getLastPID() {
return m_lastPID;
}
/**
* Cause the given PID to never be generated by the PID generator.
*/
public synchronized void neverGeneratePID(String pid) throws IOException {
LOG.debug("Never generating PID: " + pid);
try {
PID p = new PID(pid);
String ns = p.getNamespaceId();
int id = Integer.parseInt(p.getObjectId());
if (id > getHighestID(ns)) {
setHighestID(ns, id);
}
} catch (MalformedPIDException mpe) {
throw new IOException(mpe.getMessage());
} catch (NumberFormatException nfe) {
// if the id part is not numeric, we already know we'll
// never generate that id because all generated ids are numeric.
}
}
/**
* Gets the highest id ever used for the given namespace.
*/
private int getHighestID(String namespace) {
Integer i = (Integer) m_highestID.get(namespace);
if (i == null) {
return 0;
}
return i.intValue();
}
/**
* Sets the highest id ever used for the given namespace.
*/
private void setHighestID(String namespace, int id) throws IOException {
LOG.debug("Setting highest ID for " + namespace + " to " + id);
m_highestID.put(namespace, new Integer(id));
// write the new highest id in the database, too
Connection conn = null;
try {
conn = m_connectionPool.getConnection();
SQLUtility.replaceInto(conn,
"pidGen",
new String[] {"namespace", "highestID"},
new String[] {namespace, "" + id},
"namespace",
new boolean[] {false, true});
} catch (SQLException sqle) {
throw new IOException("Error setting highest id for "
+ "namespace in db: " + sqle.getMessage());
} finally {
if (conn != null) {
m_connectionPool.free(conn);
}
}
}
}