/*
*
* *
* * Licensed to the Apache Software Foundation (ASF) under one
* * or more contributor license agreements. See the NOTICE file
* * distributed with this work for additional information
* * regarding copyright ownership. The ASF licenses this file
* * to you 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.
* *
*
*/
package org.apache.usergrid.persistence.core.migration.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Preconditions;
/**
* A set that represents a set of tuples that are used for
* @param <T>
*/
public class VersionedMigrationSet<T extends VersionedData> {
/**
* Cache so that after our initial lookup, it O(1) since this will be used heavily
*
*/
private Map<Integer, MigrationRelationship<T>> cacheVersion = new HashMap<>();
private List<MigrationRelationship<T>> orderedVersions = new ArrayList<>();
/**
* Construct this set from a group of tuples. Imagine the following versions
*
* v1,
* v2,
* v3,
* v4
*
* Migrations can jump from v1->v3, but not directly to v4 without an extraneous migration. This would have 2 relationships
*
* v1, v3
* v2, v3
* and
* v3, v4
*
*
* @param migrations
*/
public VersionedMigrationSet( final MigrationRelationship<T>... migrations ){
Preconditions.checkNotNull(migrations, "versions must not be null");
Preconditions.checkArgument( migrations.length > 0, "You must specify at least 1 migrationrelationship" );
orderedVersions.addAll( Arrays.asList(migrations ) );
Collections.sort( orderedVersions, new VersionedDataComparator() );
}
/**
* Get the migration relationship based on our current version. This will return a range that includes the current
* system version as the source, and the highest version we can roll to in the to field
*
* @return The MigrationRelationship. Note the from and the to could be the same version in a current system.
*/
public MigrationRelationship<T> getMigrationRelationship( final int currentVersion ) {
final MigrationRelationship<T> relationship = cacheVersion.get( currentVersion );
if ( relationship != null ) {
return relationship;
}
//not there, find it. Not the most efficient, but it happens once per version, which rarely changes, so not
// a big deal
int lastSpan = Integer.MAX_VALUE;
MigrationRelationship<T> toUse = null;
for ( MigrationRelationship<T> current : orderedVersions ) {
//not our instance, the from is too high
//our from is this instance, so we support this tuple. Our future is >= as well, so we can perform this I/O
final int newSpan = current.getSpan( currentVersion );
if ( newSpan < lastSpan ) {
lastSpan = newSpan;
toUse = current;
}
}
//if we get here, something is wrong
if ( lastSpan == Integer.MAX_VALUE ) {
throw new IllegalArgumentException(
"Could not find a migration version for version " + currentVersion + " min found was " + orderedVersions
.get( orderedVersions.size() - 1 ) );
}
cacheVersion.put( currentVersion, toUse );
return toUse;
}
/**
* Given the current system version, return the maximum migration version we can move to
* @param currentVersion
* @return
*/
public int getMaxVersion(final int currentVersion){
return getMigrationRelationship( currentVersion ).to.getImplementationVersion();
}
/**
* Orders from high to low
*/
private final class VersionedDataComparator implements Comparator<MigrationRelationship<T>>
{
@Override
public int compare( final MigrationRelationship<T> o1, final MigrationRelationship<T> o2 ) {
//Put higher target version first, since that's what we want to match based on current state and source
//order by the source. Put highest source first
int compare = Integer.compare( o1.to.getImplementationVersion(), o2.to.getImplementationVersion() ) *-1;
//put higher from first, if we fall within a range here we're good
if(compare == 0){
compare = Integer.compare( o1.from.getImplementationVersion(), o2.from.getImplementationVersion() ) *-1;
}
return compare;
}
}
}