All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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