/* 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.storage.lowlevel; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Enumeration; import java.util.Map; import java.util.NoSuchElementException; import org.apache.log4j.Logger; import fedora.server.errors.LowlevelStorageException; import fedora.server.errors.LowlevelStorageInconsistencyException; import fedora.server.errors.ObjectNotInLowlevelStorageException; import fedora.server.storage.ConnectionPool; import fedora.server.utilities.SQLUtility; /** * @author Bill Niebel */ public class DBPathRegistry extends PathRegistry { /** Logger for this class. */ private static final Logger LOG = Logger.getLogger(DBPathRegistry.class.getName()); private ConnectionPool connectionPool = null; private final boolean backslashIsEscape; public DBPathRegistry(Map<String, ?> configuration) { super(configuration); connectionPool = (ConnectionPool) configuration.get("connectionPool"); backslashIsEscape = Boolean .valueOf((String) configuration .get("backslashIsEscape")).booleanValue(); } @Override public String get(String pid) throws ObjectNotInLowlevelStorageException, LowlevelStorageInconsistencyException, LowlevelStorageException { String path = null; Connection connection = null; Statement statement = null; ResultSet rs = null; try { int paths = 0; connection = connectionPool.getConnection(); statement = connection.createStatement(); rs = statement.executeQuery("SELECT path FROM " + getRegistryName() + " WHERE token='" + pid + "'"); for (; rs.next(); paths++) { path = rs.getString(1); } if (paths == 0) { throw new ObjectNotInLowlevelStorageException("no path in db registry for [" + pid + "]"); } if (paths > 1) { throw new LowlevelStorageInconsistencyException("[" + pid + "] in db registry -multiple- times"); } if (path == null || path.length() == 0) { throw new LowlevelStorageInconsistencyException("[" + pid + "] has -null- path in db registry"); } } catch (SQLException e1) { throw new LowlevelStorageException(true, "sql failure (get)", e1); } finally { try { if (rs != null) { rs.close(); } if (statement != null) { statement.close(); } if (connection != null) { connectionPool.free(connection); } } catch (Exception e2) { // purposely general to include uninstantiated statement, connection throw new LowlevelStorageException(true, "sql failure closing statement, connection, pool (get)", e2); } finally { rs = null; statement = null; } } return path; } public void executeSql(String sql) throws ObjectNotInLowlevelStorageException, LowlevelStorageInconsistencyException, LowlevelStorageException { Connection connection = null; Statement statement = null; try { connection = connectionPool.getConnection(); statement = connection.createStatement(); if (statement.execute(sql)) { throw new LowlevelStorageException(true, "sql returned query results for a nonquery"); } int updateCount = statement.getUpdateCount(); if (updateCount == 0) { throw new ObjectNotInLowlevelStorageException("-no- rows updated in db registry"); } if (updateCount > 1) { throw new LowlevelStorageInconsistencyException("-multiple- rows updated in db registry"); } } catch (SQLException e1) { throw new LowlevelStorageException(true, "sql failurex (exec)", e1); } finally { try { if (statement != null) { statement.close(); } if (connection != null) { connectionPool.free(connection); } } catch (Exception e2) { // purposely general to include uninstantiated statement, connection throw new LowlevelStorageException(true, "sql failure closing statement, connection, pool (exec)", e2); } finally { statement = null; } } } @Override public void put(String pid, String path) throws ObjectNotInLowlevelStorageException, LowlevelStorageInconsistencyException, LowlevelStorageException { if (backslashIsEscape) { StringBuffer buffer = new StringBuffer(); String backslash = "\\"; //Java quotes will interpolate this as 1 backslash String escapedBackslash = "\\\\"; //Java quotes will interpolate these as 2 backslashes /* * Escape each backspace so that DB will correctly record a single * backspace, instead of incorrectly escaping the following * character. */ for (int i = 0; i < path.length(); i++) { String s = path.substring(i, i + 1); buffer.append(s.equals(backslash) ? escapedBackslash : s); } path = buffer.toString(); } Connection conn = null; try { conn = connectionPool.getConnection(); SQLUtility.replaceInto(conn, getRegistryName(), new String[] { "token", "path"}, new String[] {pid, path}, "token"); } catch (SQLException e1) { throw new ObjectNotInLowlevelStorageException("put into db registry failed for [" + pid + "]", e1); } finally { if (conn != null) { connectionPool.free(conn); } } } @Override public void remove(String pid) throws ObjectNotInLowlevelStorageException, LowlevelStorageInconsistencyException, LowlevelStorageException { try { executeSql("DELETE FROM " + getRegistryName() + " WHERE " + getRegistryName() + ".token='" + pid + "'"); } catch (ObjectNotInLowlevelStorageException e1) { throw new ObjectNotInLowlevelStorageException("[" + pid + "] not in db registry to delete", e1); } catch (LowlevelStorageInconsistencyException e2) { throw new LowlevelStorageInconsistencyException("[" + pid + "] deleted from db registry -multiple- times", e2); } } @Override public void rebuild() throws LowlevelStorageException { int report = FULL_REPORT; try { executeSql("DELETE FROM " + getRegistryName()); } catch (ObjectNotInLowlevelStorageException e1) { } catch (LowlevelStorageInconsistencyException e2) { } try { LOG.info("begin rebuilding registry from files"); traverseFiles(storeBases, REBUILD, false, report); // continues, ignoring bad files LOG.info("end rebuilding registry from files (ending normally)"); } catch (Exception e) { if (report != NO_REPORT) { LOG.error("ending rebuild unsuccessfully", e); } throw new LowlevelStorageException(true, "ending rebuild unsuccessfully", e); //<<==== } } @Override public void auditFiles() throws LowlevelStorageException { LOG.info("begin audit: files-against-registry"); traverseFiles(storeBases, AUDIT_FILES, false, FULL_REPORT); LOG.info("end audit: files-against-registry (ending normally)"); } @Override protected Enumeration<String> keys() throws LowlevelStorageException, LowlevelStorageInconsistencyException { File tempFile = null; PrintWriter writer = null; ResultSet rs = null; Connection connection = null; Statement statement = null; try { tempFile = File.createTempFile("fedora-keys", ".tmp"); writer = new PrintWriter(new OutputStreamWriter( new FileOutputStream(tempFile))); connection = connectionPool.getConnection(); statement = connection.createStatement(); rs = statement.executeQuery("SELECT token FROM " + getRegistryName()); while (rs.next()) { String key = rs.getString(1); if (null == key || 0 == key.length()) { throw new LowlevelStorageInconsistencyException( "Null token found in " + getRegistryName()); } writer.println(key); } writer.close(); return new KeyEnumeration(tempFile); } catch (Exception e) { throw new LowlevelStorageException(true, "Unexpected error", e); } finally { try { if (rs != null) { rs.close(); } if (statement != null) { statement.close(); } if (connection != null) { connectionPool.free(connection); } } catch (Exception e) { throw new LowlevelStorageException(true, "Unexpected error", e); } finally { writer.close(); rs = null; statement = null; } } } /** * Iterates over each non-empty line in a temporary file. * When iteration is complete, or garbage collection occurs, the * file will be deleted. */ private class KeyEnumeration implements Enumeration<String> { private final File file; private final BufferedReader reader; private boolean closed; private String nextKey; public KeyEnumeration(File file) throws FileNotFoundException { this.file = file; this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); setNextKey(); } private void setNextKey() { try { nextKey = reader.readLine(); if (nextKey == null) { close(); } else if (nextKey.length() == 0) { setNextKey(); } } catch (IOException e) { throw new Error(e); } } public boolean hasMoreElements() { return nextKey != null; } public String nextElement() { if (nextKey != null) { try { return nextKey; } finally { setNextKey(); } } else { throw new NoSuchElementException(); } } @Override protected void finalize() { if (!closed) { close(); } } private void close() { try { reader.close(); file.delete(); } catch (IOException e) { throw new Error(e); } finally { closed = true; } } } }