/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.util.localdb; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.util.java.FileSystemUtility; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import java.io.File; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Map; import java.util.Properties; /** * Apache Derby Wrapper for {@link LocalDB} interface. Uses a single table per DB, with * two columns each. This class would be easily adaptable for a generic JDBC implementation. * * @author Jason D. Rivard */ public class Derby_LocalDB extends AbstractJDBC_LocalDB { private static final PwmLogger LOGGER = PwmLogger.forClass(Derby_LocalDB.class, true); private static final String DERBY_CLASSPATH = "org.apache.derby.jdbc.EmbeddedDriver"; private static final String DERBY_DEFAULT_SCHEMA = "APP"; private Driver driver; Derby_LocalDB() throws Exception { super(); } @Override String getDriverClasspath() { return DERBY_CLASSPATH; } @Override void closeConnection(final Connection connection) throws SQLException { try { if (driver != null) { driver.connect("jdbc:derby:;shutdown=true", new Properties()); } } catch (SQLException e) { if ("XJ015".equals(e.getSQLState())) { LOGGER.trace("Derby shutdown succeeded. SQLState=" + e.getSQLState() + ", message=" + e.getMessage()); } else { throw e; } } try { if (driver != null) { DriverManager.deregisterDriver(driver); } } catch (Exception e) { LOGGER.error("error while de-registering derby driver: " + e.getMessage()); } driver = null; try { connection.close(); } catch (Exception e) { LOGGER.error("error while closing derby connection: " + e.getMessage()); } } @Override Connection openConnection( final File databaseDirectory, final String driverClasspath, final Map<String,String> initOptions ) throws LocalDBException { final String filePath = databaseDirectory.getAbsolutePath() + File.separator + "derby-db"; final String baseConnectionURL = "jdbc:derby:" + filePath; final String connectionURL = baseConnectionURL + ";create=true"; try { driver = (Driver)Class.forName(driverClasspath).newInstance(); //load driver. final Connection connection = driver.connect(connectionURL, new Properties()); connection.setAutoCommit(false); if (aggressiveCompact) { reclaimAllSpace(connection); } return connection; } catch (Throwable e) { final String errorMsg; if (e instanceof SQLException) { final SQLException sqlException = (SQLException)e; final SQLException nextException = sqlException.getNextException(); if (nextException != null) { if ("XSDB6".equals(nextException.getSQLState())) { errorMsg = "unable to open LocalDB, the LocalDB is already opened in a different instance: " + nextException.getMessage(); } else { errorMsg = "unable to open LocalDB, error=" + e.getMessage() + ", nextError=" + nextException.getMessage(); } } else { errorMsg = "unable to open LocalDB, error=" + e.getMessage(); } } else { errorMsg = "error opening DB: " + e.getMessage(); } LOGGER.error(errorMsg, e); throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,errorMsg)); } } private void reclaimAllSpace(final Connection dbConnection) { final java.util.Date startTime = new java.util.Date(); final long startSize = FileSystemUtility.getFileDirectorySize(dbDirectory); LOGGER.debug("beginning reclaim space in all tables startSize=" + StringUtil.formatDiskSize(startSize)); for (final LocalDB.DB db : LocalDB.DB.values()) { reclaimSpace(dbConnection,db); } final long completeSize = FileSystemUtility.getFileDirectorySize(dbDirectory); final long sizeDifference = startSize - completeSize; LOGGER.debug("completed reclaim space in all tables; duration=" + TimeDuration.fromCurrent(startTime).asCompactString() + ", startSize=" + StringUtil.formatDiskSize(startSize) + ", completeSize=" + StringUtil.formatDiskSize(completeSize) + ", sizeDifference=" + StringUtil.formatDiskSize(sizeDifference) ); } public void truncate(final LocalDB.DB db) throws LocalDBException { super.truncate(db); reclaimSpace(this.dbConnection, db); } private void reclaimSpace(final Connection dbConnection, final LocalDB.DB db) { if (readOnly) { return; } final long startTime = System.currentTimeMillis(); CallableStatement statement = null; try { LOCK.writeLock().lock(); LOGGER.debug("beginning reclaim space in table " + db.toString()); statement = dbConnection.prepareCall("CALL SYSCS_UTIL.SYSCS_INPLACE_COMPRESS_TABLE(?, ?, ?, ?, ?)"); statement.setString(1, DERBY_DEFAULT_SCHEMA); statement.setString(2, db.toString()); statement.setShort(3, (short) 1); statement.setShort(4, (short) 1); statement.setShort(5, (short) 1); statement.execute(); } catch (SQLException ex) { LOGGER.error("error reclaiming space in table " + db.toString() + ": " + ex.getMessage()); } finally { close(statement); LOCK.writeLock().unlock(); } LOGGER.debug("completed reclaimed space in table " + db.toString() + " (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")"); } }