/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.util;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import org.apache.lucene.index.SegmentCommitInfo;
import org.apache.lucene.index.SegmentInfos;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
/**
* This utility class simply determines the version of a given Solr/Lucene index,
* so that they can be upgraded to the latest version.
* <p>
* You must pass it the full path of the index directory, e.g.
* {@code [dspace]/solr/statistics/data/index/}
* <p>
* The response is simply a version number (e.g. 4.4), as this is utilized by
* the {@code ant update_solr_indexes} target in {@code [src]/dspace/src/main/config/build.xml}
*
* @author tdonohue
*/
public class IndexVersion
{
public static void main(String[] argv)
throws IOException
{
// Usage checks
if (argv.length < 1)
{
System.out.println("\nRequired Solr/Lucene index directory is missing.");
System.out.println("Minimally, pass in the full path of the Solr/Lucene Index directory to analyze");
System.out.println("Usage: IndexVersion [full-path-to-solr-index] ([version-to-compare])");
System.out.println(" - [full-path-to-index] is REQUIRED (e.g. [dspace.dir]/solr/statistics/data/index/)");
System.out.println(" - [version-to-compare] is optional. When specified, this command will return:");
System.out.println(" -1 if index dir version < version-to-compare");
System.out.println(" 0 if index dir version = version-to-compare");
System.out.println(" 1 if index dir version > version-to-compare");
System.out.println("\nOptionally, passing just '-v' will return the version of Solr/Lucene in use by DSpace.");
System.exit(1);
}
// If "-v" passed on commandline, just return the current version of Solr/Lucene
if(argv[0].equalsIgnoreCase("-v"))
{
System.out.println(getLatestVersion());
System.exit(0);
}
// First argument is the Index path. Determine its version
String indexVersion = getIndexVersion(argv[0]);
// Second argumet is an optional version number to compare to
String compareToVersion = argv.length > 1 ? argv[1] : null;
// If indexVersion comes back as null, then it is not a valid index directory.
// So, exit immediately.
if(indexVersion==null)
{
System.out.println("\nRequired Solr/Lucene index directory is invalid.");
System.out.println("The following path does NOT seem to be a valid index directory:");
System.out.println(argv[0]);
System.out.println("Please pass in the full path of the Solr/Lucene Index directory to analyze");
System.out.println("(e.g. [dspace.dir]/solr/statistics/data/index/)\n");
System.exit(1);
}
// If empty string is returned as the indexVersion, this means it's an
// empty index directory. So, it's technically "compatible" with any version of Lucene.
if (indexVersion.equals(""))
{
indexVersion = getLatestVersion();
}
// If a compare-to-version was passed in, print the result of this comparison
if(compareToVersion!=null && !compareToVersion.isEmpty())
{
// If the string "LATEST" is passed, determine which version of Lucene API we are using.
if(compareToVersion.equalsIgnoreCase("LATEST"))
{
compareToVersion = getLatestVersion();
}
System.out.println(compareSoftwareVersions(indexVersion,compareToVersion));
}
// Otherwise, we'll just print the version of this index directory
else
{
System.out.println(indexVersion);
}
System.exit(0);
}
/**
* Determine the version of Solr/Lucene which was used to create a given index directory.
*
* @param indexDirPath
* Full path of the Solr/Lucene index directory
* @return version as a string (e.g. "4.4"), empty string ("") if index directory is empty,
* or null if directory doesn't exist.
* @throws IOException if IO error
*/
public static String getIndexVersion(String indexDirPath)
throws IOException
{
String indexVersion = null;
// Make sure this directory exists
File dir = new File(indexDirPath);
if(dir.exists() && dir.isDirectory())
{
// Check if this index directory has any contents
String[] dirContents = dir.list();
// If this directory is empty, return an empty string.
// It is a valid directory, but it's an empty index.
if (dirContents!=null && dirContents.length==0)
{
return "";
}
// Open this index directory in Lucene
Directory indexDir = FSDirectory.open(dir);
// Get info on the Lucene segment file(s) in index directory
SegmentInfos sis = new SegmentInfos();
try
{
sis.read(indexDir);
}
catch(IOException ie)
{
// Wrap default IOException, providing more info about which directory cannot be read
throw new IOException("Could not read Lucene segments files in " + dir.getAbsolutePath(), ie);
}
// If we have a valid Solr index dir, but it has no existing segments
// then just return an empty string. It's a valid but empty index.
if(sis!=null && sis.size()==0)
{
return "";
}
// Loop through our Lucene segment files to locate the OLDEST
// version. It is possible for individual segment files to be
// created by different versions of Lucene. So, we just need
// to find the oldest version of Lucene which created these
// index segment files.
// This logic borrowed from Lucene v.4.10 CheckIndex class:
// https://github.com/apache/lucene-solr/blob/lucene_solr_4_10/lucene/core/src/java/org/apache/lucene/index/CheckIndex.java#L426
// WARNING: It MAY require updating whenever we upgrade the
// "lucene.version" in our DSpace Parent POM
Version oldest = null;
Version oldSegment = null;
for (SegmentCommitInfo si : sis)
{
// Get the version of Lucene which created this segment file
Version version = si.info.getVersion();
if(version == null)
{
// If null, then this is a pre-3.1 segment file.
// For our purposes, we will just assume it is "3.0",
// This lets us know we will need to upgrade it to 3.5
// before upgrading to Solr/Lucene 4.x or above
try
{
oldSegment = Version.parse("3.0");
}
catch(ParseException pe)
{
throw new IOException(pe);
}
}
// else if this segment is older than our oldest thus far
else if(oldest == null || version.onOrAfter(oldest) == false)
{
// We have a new oldest segment version
oldest = version;
}
}
// If we found a really old segment, compare it to the oldest
// to see which is actually older
if(oldSegment!=null && oldSegment.onOrAfter(oldest) == false)
{
oldest = oldSegment;
}
// At this point, we should know what version of Lucene created our
// oldest segment file. We will return this as the Index version
// as it's the oldest segment we will need to upgrade.
if(oldest!=null)
{
indexVersion = oldest.toString();
}
}
return indexVersion;
}
/**
* Compare two software version numbers to see which is greater. ONLY does
* a comparison of *major* and *minor* versions (any sub-minor versions are
* stripped and ignored).
* <P>
* This method returns -1 if firstVersion is less than secondVersion,
* 1 if firstVersion is greater than secondVersion, and 0 if equal.
* <P>
* However, since we ignore sub-minor versions, versions "4.2.1" and "4.2.5"
* will be seen as EQUAL (as "4.2" = "4.2").
* <P>
* NOTE: In case it is not obvious, software version numbering does NOT
* behave like normal decimal numbers. For example, in software versions
* the following statement is TRUE: {@code 4.1 < 4.4 < 4.5 < 4.10 < 4.21 < 4.51}
*
* @param firstVersion
* First version to compare, as a String
* @param secondVersion
* Second version to compare as a String
* @return -1 if first less than second, 1 if first greater than second, 0 if equal
* @throws IOException if IO error
*/
public static int compareSoftwareVersions(String firstVersion, String secondVersion)
throws IOException
{
// Constants which represent our various return values for this comparison
int GREATER_THAN = 1;
int EQUAL = 0;
int LESS_THAN = -1;
// "null" is less than anything
if(firstVersion==null)
return LESS_THAN;
// Anything is newer than "null"
if(secondVersion==null)
return GREATER_THAN;
//Split the first version into it's parts (i.e. major & minor versions)
String[] firstParts = firstVersion.split("\\.");
String[] secondParts = secondVersion.split("\\.");
// Get major / minor version numbers. Default to "0" if unspecified
// NOTE: We are specifically IGNORING any sub-minor version numbers
int firstMajor = firstParts.length>=1 ? Integer.parseInt(firstParts[0]) : 0;
int firstMinor = firstParts.length>=2 ? Integer.parseInt(firstParts[1]) : 0;
int secondMajor = secondParts.length>=1 ? Integer.parseInt(secondParts[0]) : 0;
int secondMinor = secondParts.length>=2 ? Integer.parseInt(secondParts[1]) : 0;
// Check for equality
if(firstMajor==secondMajor && firstMinor==secondMinor)
{
return EQUAL;
}
// If first major version is greater than second
else if(firstMajor > secondMajor)
{
return GREATER_THAN;
}
// If first major version is less than second
else if(firstMajor < secondMajor)
{
return LESS_THAN;
}
// If we get here, major versions must be EQUAL. Now, time to check our minor versions
else if(firstMinor > secondMinor)
{
return GREATER_THAN;
}
else if(firstMinor < secondMinor)
{
return LESS_THAN;
}
else
{
// This is an impossible scenario.
// This 'else' should never be triggered since we've checked for equality above already
return EQUAL;
}
}
/**
* Determine the version of Solr/Lucene which DSpace is currently running.
* This is the latest version of Solr/Lucene which we can upgrade the index to.
*
* @return version as a string (e.g. "4.4")
*/
public static String getLatestVersion()
{
// The current version of lucene is in the "LATEST" constant
return org.apache.lucene.util.Version.LATEST.toString();
}
}