
it.tidalwave.netbeans.persistence.maintenance.impl.Maintainer Maven / Gradle / Ivy
The newest version!
/***********************************************************************************************************************
*
* OpenBlueSky - NetBeans Platform Enhancements
* Copyright (C) 2006-2012 by Tidalwave s.a.s. (http://www.tidalwave.it)
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* WWW: http://openbluesky.java.net
* SCM: https://bitbucket.org/tidalwave/openbluesky-src
*
**********************************************************************************************************************/
package it.tidalwave.netbeans.persistence.maintenance.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.openide.util.Lookup;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.netbeans.util.Locator;
import it.tidalwave.netbeans.persistence.Persistence;
import it.tidalwave.netbeans.persistence.TxTask;
import it.tidalwave.netbeans.persistence.maintenance.MaintainerTask;
import it.tidalwave.netbeans.persistence.impl.PersistenceImpl;
/***********************************************************************************************************************
*
* @author Fabrizio Giudici
* @version $Id$
*
**********************************************************************************************************************/
public class Maintainer
{
private static final String CLASS = Maintainer.class.getName();
private static final Logger logger = Logger.getLogger(CLASS);
private static final String CREATE_TABLE_VERSION_SQL = "CREATE TABLE TABLE_VERSION (TABLE_NAME VARCHAR(200) PRIMARY KEY, VERSION INTEGER NOT NULL)";
private final Persistence persistence = Locator.find(Persistence.class);
protected final Map> maintainerMapByPair =
new HashMap>();
private final List manipulators = new ArrayList();
private final SortedSet entitiesToUpdate = new TreeSet();
private Connection connection;
/***************************************************************************
*
*
**************************************************************************/
public Maintainer()
{
loadMaintainerTasks();
}
/***************************************************************************
*
*
**************************************************************************/
public void run()
throws Exception
{
runPhaseOne();
runPhaseTwo();
}
/***************************************************************************
*
* Returns true if the given table exists.
*
* @param tableName the name of the table
* @return true if the table exists
*
**************************************************************************/
public boolean existsTable (final String tableName)
throws SQLException
{
final DatabaseMetaData metaData = connection.getMetaData();
final ResultSet rs = metaData.getTables(null, null, tableName, null);
final boolean exists = rs.next();
rs.close();
return exists;
}
/***************************************************************************
*
* Executes all the required steps to update the table related to the given
* entity class.
*
* @param entityAndTablePair the entity class and table
* @return
*
**************************************************************************/
public TableManipulator updateEntity (final EntityAndTablePair entityAndTablePair)
throws SQLException
{
logger.fine("updateEntity(%s)", entityAndTablePair);
final Map maintainerMapByVersion = maintainerMapByPair.get(entityAndTablePair);
entitiesToUpdate.remove(entityAndTablePair);
if (maintainerMapByVersion == null) // nothing to do
{
// return null;
return new TableManipulator(entityAndTablePair, this, connection);
}
final TableManipulator manipulator = new TableManipulator(entityAndTablePair, this, connection);
final String tableName = entityAndTablePair.getTableName();
//
// If the table doesn't exist, this means that we are initializing the database from
// scratch. In this case, this means that we don't have to do anything and the table
// will be created with the latest available version.
//
if (!existsTable(tableName))
{
int version = Integer.MIN_VALUE;
MaintainerTask m = null;
for (final MaintainerTask maintainerTask : maintainerMapByVersion.values())
{
final int newVersion = maintainerTask.getToVersion();
if (newVersion > version)
{
m = maintainerTask;
version = newVersion;
}
}
manipulator.setVersion(version);
logger.info("Table %s will be created from scratch with version = %d", tableName, version);
manipulator.initialize();
m.run(this, manipulator);
return manipulator;
}
manipulators.add(manipulator);
//
// Keep version updated without calling getVersion() further times: getVersion()
// reads the database and will always return the same value until the transaction commits.
//
int version = manipulator.getVersion();
logger.info("Table %s exists with version = %d", tableName, version);
boolean first = true;
for (;;)
{
final MaintainerTask maintainerTask = maintainerMapByVersion.get(version);
if (maintainerTask == null)
{
break;
}
if (first)
{
manipulator.initialize(); // (maintainerTask.getOrigTableName());
manipulator.createTemporaryTable();
first = false;
}
final int toVersion = maintainerTask.getToVersion();
logger.info(">>>> running maintainerTask for %s, v%d -> v%d", entityAndTablePair, version, toVersion);
maintainerTask.run(this, manipulator);
manipulator.setVersion(toVersion);
version = toVersion;
}
return manipulator;
}
/***************************************************************************
*
* Returns the current version of the managed table.
*
* @return the version
* @throws SQLException
*
**************************************************************************/
public int getVersion (final String tableName)
throws SQLException
{
logger.fine("getVersion(%s)", tableName);
final String sql = "SELECT VERSION FROM TABLE_VERSION WHERE TABLE_NAME=?";
final PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, tableName);
final ResultSet rs = ps.executeQuery();
int version = -1;
if (rs.next())
{
version = rs.getInt("VERSION");
}
rs.close();
ps.close();
if (version < 0)
{
setVersion(tableName, version = 0);
}
logger.finer(">>>> version for %s: %d", tableName, version);
return version;
}
/***************************************************************************
*
* Sets the current version of the managed table.
*
* @param version the version
* @throws SQLException
*
**************************************************************************/
public void setVersion (final String tableName, final int version)
throws SQLException
{
logger.fine("setVersion(%s, %d)", tableName, version);
final String sql = "UPDATE TABLE_VERSION SET VERSION=? WHERE TABLE_NAME=?";
final PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1, version);
ps.setString(2, tableName);
final int n = ps.executeUpdate();
ps.close();
if (n == 0)
{
final String sql2 = "INSERT INTO TABLE_VERSION(TABLE_NAME,VERSION) VALUES(?,?)";
final PreparedStatement ps2 = connection.prepareStatement(sql2);
ps2.setString(1, tableName);
ps2.setInt(2, version);
ps2.executeUpdate();
ps2.close();
}
}
/***************************************************************************
*
* Phase 1 of maintainance is executed before JPA boots, so it must only
* rely on plain JDBC stuff. It will execute all the {@link MaintainerTask}s
* available.
*
**************************************************************************/
private void runPhaseOne()
throws Exception
{
logger.info("Phase one maintainance started");
openConnection();
createTableVersionTable();
manipulators.clear();
entitiesToUpdate.addAll(maintainerMapByPair.keySet());
try
{
while (!entitiesToUpdate.isEmpty())
{
updateEntity(entitiesToUpdate.first());
}
}
catch (Exception e)
{
logger.severe("Fatal error while running Maintainer");
logger.throwing(CLASS, "run()", e);
}
for (final TableManipulator manipulator : manipulators)
{
manipulator.close();
}
closeConnection();
// FIXME: if an exception was triggered, stop here
logger.info("Phase one maintainance done, closed connection");
}
/***************************************************************************
*
* Phase 2 of the maintainance consists in having JPA starting, so the
* entity tables will be created, and then running
* {@link TableManipulator#restoreTable()} on all manipulators, so data are
* copied.
*
**************************************************************************/
private void runPhaseTwo()
{
logger.info("Phase two maintainance started - now starting JPA...");
persistence.getEntityManagerFactory(); // This will create tables according to JPA annotations
TxTask.execute(new TxTask()
{
@Override
public Void run()
{
for (final TableManipulator manipulator : manipulators)
{
manipulator.restoreTable();
}
return null;
}
});
logger.info("Phase two maintainance done");
}
/***************************************************************************
*
*
**************************************************************************/
protected synchronized void openConnection()
throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException
{
logger.fine("openConnection()");
if (connection == null)
{
final Properties properties = ((PersistenceImpl)persistence).getProperties();
final String dbURL = properties.getProperty("hibernate.connection.url");
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final Class driverClass = (Class)classLoader.loadClass(properties.getProperty("hibernate.connection.driver_class"));
final Driver driver = driverClass.newInstance();
connection = driver.connect(dbURL, properties);
connection.setAutoCommit(false);
}
}
/***************************************************************************
*
*
**************************************************************************/
protected synchronized void closeConnection()
throws SQLException
{
logger.fine("closeConnection()");
if (connection != null)
{
connection.commit();
connection.close();
connection = null;
}
}
/***************************************************************************
*
* Creates the TABLE_VERSION table if it doesn' exist.
*
**************************************************************************/
protected void createTableVersionTable()
throws SQLException
{
if (!existsTable("TABLE_VERSION"))
{
logger.info("Creating TABLE_VERSION...");
final Statement stat = connection.createStatement();
stat.executeUpdate(CREATE_TABLE_VERSION_SQL);
stat.close();
}
}
/***************************************************************************
*
* Loads all the {@link MaintainerTask}s in modules.
*
**************************************************************************/
private void loadMaintainerTasks()
{
for (final MaintainerTask tableUpdater : Lookup.getDefault().lookupAll(MaintainerTask.class))
{
final EntityAndTablePair pair = tableUpdater.getEntityAndTablePair();
final int fromVersion = tableUpdater.getFromVersion();
Map maintainerMapByVersion = maintainerMapByPair.get(pair);
if (maintainerMapByVersion == null)
{
maintainerMapByVersion = new HashMap();
maintainerMapByPair.put(pair, maintainerMapByVersion);
}
if (maintainerMapByVersion.containsKey(fromVersion))
{
throw new RuntimeException(String.format("Duplicate maintainer for class %s, version: %d", pair, fromVersion));
}
maintainerMapByVersion.put(fromVersion, tableUpdater);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy