/**
* Copyright (c) 2002-2010 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.util;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
/**
* Used for migrating data between one version of the code to a newer version.
* Migration is done typically when the data structure needs changes and some
* adaptation to work with the new version of the code.
*
* Versions begin at zero and increases one per version. Version zero needs
* no migration. So if the "code version" is zero then no migration is
* beeing done. If "code version" is more than zero and "data version" is
* less than "code version" then instances of {@link Migrator} are created
* and executed for each differentiating version.
*/
public abstract class Migration
{
private GraphDatabaseService graphDb;
private Node configNode;
private boolean firstVersionIsAlwaysZero;
private boolean pretending;
/**
* Creates a new migration object with a reference to a configuration node.
* @param configNode the node to hold configuration data for migration.
* @param graphDb the {@link GraphDatabaseService} to use.
* This node should be the same every time in a code base.
*/
public Migration( GraphDatabaseService graphDb, Node configNode )
{
this.graphDb = graphDb;
this.configNode = configNode;
}
private Node getConfigNode()
{
return this.configNode;
}
protected String getCurrentVersionPropertyKey()
{
return "current_version";
}
/**
* Used as a lookup to find a {@link Migrator} instance for a specific
* version. The first migrator has version one.
* @param version which version to find a {@link Migrator} for.
* @return the {@link Migrator} for <code>version</code>.
*/
protected abstract Migrator findMigrator( int version );
/**
* Implemented by the client class to tell the migration unit which version
* the code is. If the returned value is zero then nothing is done.
* Actual migration starts at version one.
* @return the version of the code.
*/
protected abstract int getCodeVersion();
/**
* Returns the version of the data. The first call to this method will
* return the same version as "code version", and increase as
* "code version" increases and calls to {@link #syncVersion()} are made.
* @return the version of the data.
*/
public int getDataVersion()
{
Transaction tx = graphDb.beginTx();
try
{
int result = 0;
String key = getCurrentVersionPropertyKey();
if ( this.getConfigNode().hasProperty( key ) )
{
result = ( Integer ) this.getConfigNode().getProperty( key );
}
else
{
result = this.firstVersionIsAlwaysZero ?
// This causes all the migrators to run each time a new
// database is created, but it should be ok since it's
// empty then.
0 :
// This is how it should be, but only if the Migration class
// is used by the client from the beginning of the
// development There is a problem if the migration class
// starts getting used after a while, when the first
// migrator is written.
this.getCodeVersion();
this.getConfigNode().setProperty( key, result );
}
if ( this.firstVersionIsAlwaysZero )
{
result = ( Integer ) this.getConfigNode().getProperty( key, 0 );
}
else
{
if ( this.getConfigNode().hasProperty( key ) )
{
result = ( Integer ) this.getConfigNode().getProperty(
key );
}
else
{
result = this.getCodeVersion();
this.getConfigNode().setProperty( key, result );
}
}
tx.success();
return result;
}
finally
{
tx.finish();
}
}
/**
* Sets the data version to <code>version</code>. This is really only
* used internally to update the data version after each migrated version,
* but can be helpful if a problem should arise and some version will
* have to be migrated again.
* @param version the new data version.
*/
public void setDataVersion( int version )
{
Transaction tx = graphDb.beginTx();
try
{
this.getConfigNode().setProperty( getCurrentVersionPropertyKey(),
version );
tx.success();
}
finally
{
tx.finish();
}
}
/**
* Useful for testing purposes. If <code>pretend</code> is
* <code>true</code> all needed migration will be done, but not committed.
* @param pretend wether ot nor to pretend the acttual migration.
*/
public void setPretend( boolean pretend )
{
this.pretending = pretend;
}
/**
* Makes the "data version" to be zero the first time, instead of
* same as the "code version".
* @param firstIsZero whether or not to enable this functionality.
*/
public void setFirstVersionIsAlwaysZero( boolean firstIsZero )
{
this.firstVersionIsAlwaysZero = firstIsZero;
}
/**
* This is the method which should be called by the client to tell this
* migration unit to look at differences between {@link #getDataVersion()}
* and "code version" and migrate accordingly from the
* {@link #getDataVersion()} to the "code version".
* @throws RuntimeException if the migration (or parts of it) couldn't
* be done. It describes which version failed.
*/
public void syncVersion()
{
int codeVersion = this.getCodeVersion();
int dataVersion = this.getDataVersion();
if ( codeVersion == dataVersion )
{
// The data version is the same as the code version
return;
}
if ( codeVersion < dataVersion )
{
throw new RuntimeException(
"Backwards migration not supported" );
}
int versionToMigrate = 0;
try
{
for ( versionToMigrate = dataVersion + 1;
versionToMigrate <= codeVersion; versionToMigrate++ )
{
migrateOne( versionToMigrate );
}
}
catch ( Exception e )
{
throw new RuntimeException( "Migration[v" + dataVersion + "-v" +
codeVersion + "] FAILED, specifically v" +
versionToMigrate, e );
}
}
private void migrateOne( int version )
{
Transaction tx = graphDb.beginTx();
try
{
System.out.println( "Migrating ==> version " + version );
findMigrator( version ).performMigration( this.graphDb );
setDataVersion( version );
if ( !this.pretending )
{
tx.success();
}
}
finally
{
tx.finish();
}
}
}