/*==========================================================================*\
| $Id: FeatureDescriptor.java,v 1.10 2011/05/27 15:30:56 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2011 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT 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.
|
| Web-CAT 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 General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package net.sf.webcat;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import net.sf.webcat.WCUpdater.Condition;
// -------------------------------------------------------------------------
/**
* This class represents the key properties of an updatable Web-CAT feature,
* such as a subsystem or a plug-in. The key properties include
* its version, its provider, and where updates can be obtained on the
* web.
*
* @author stedwar2
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.10 $, $Date: 2011/05/27 15:30:56 $
*/
public class FeatureDescriptor
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new feature descriptor.
* @param name the name of this feature (e.g., "Core")
* @param properties the properties object that defines the
* characteristics of this subsystem
* @param isPlugin false if this is a subsystem, true if it is a plug-in
*/
public FeatureDescriptor(
String name, Properties properties, boolean isPlugin )
{
this.name = name;
this.properties = properties;
this.isPlugin = isPlugin;
}
// ----------------------------------------------------------
/**
* For subclass use.
*/
protected FeatureDescriptor()
{
// Subclass is responsible for initializing data fields
}
//~ Public Constants ......................................................
public static final String PLUGIN_NAME_PREFIX = "plugin.";
public static final String SUBSYSTEM_NAME_PREFIX = "subsystem.";
public static final String RENAME_SUFFIX = ".renameTo";
//~ Public Methods ........................................................
// ----------------------------------------------------------
/**
* Get this subsystem's symbolic name, e.g. "grader" or "core".
* @return the symbolic name of this subsystem
*/
public String name()
{
return name;
}
// ----------------------------------------------------------
/**
* Determine if this is a real subsystem, or just a proxy for a
* non-registered framework on the classpath.
* @return True if this is a registered subsystem
*/
public boolean isRegistered()
{
return name != null;
}
// ----------------------------------------------------------
/**
* Determine if this is a subsystem or a plug-in descriptor.
* @return True if this is a plug-in descriptor
*/
public boolean isPlugin()
{
return isPlugin;
}
// ----------------------------------------------------------
/**
* Get the version number for the currently installed version of this
* subsystem.
* @return the current subsystem version as a string, like "1.2.3"
*/
public String currentVersion()
{
if ( currentVersion == null )
{
currentVersion = "" + versionMajor() + "." + versionMinor()
+ "." + versionRevision();
}
return currentVersion;
}
// ----------------------------------------------------------
/**
* Get the major version number for this subsystem.
* @return the subsystem's major version number
*/
public int versionMajor()
{
if ( versionMajor < 0 )
{
versionMajor = intProperty( "version.major" );
}
return versionMajor;
}
// ----------------------------------------------------------
/**
* Get the minor version number for this subsystem.
* @return the subsystem's minor version number
*/
public int versionMinor()
{
if ( versionMinor < 0 )
{
versionMinor = intProperty( "version.minor" );
}
return versionMinor;
}
// ----------------------------------------------------------
/**
* Get the revision number for this subsystem.
* @return the subsystem's revision number
*/
public int versionRevision()
{
if ( versionRevision < 0 )
{
versionRevision = intProperty( "version.revision" );
}
return versionRevision;
}
// ----------------------------------------------------------
/**
* Get the build date for this subsystem.
* @return the subsystem's build date
*/
public String versionDate()
{
return getProperty( "version.date" );
}
// ----------------------------------------------------------
/**
* Get the build date for this subsystem.
* @return the subsystem's build date
*/
public String description()
{
return getProperty( "description" );
}
// ----------------------------------------------------------
/**
* Get a proxy for this subsystem's provider, which can be used to
* dynamically download a new version of this subsystem.
* @return the subsystem's provider
* @throws IOException
*/
public FeatureProvider provider() throws IOException
{
return FeatureProvider.getProvider(getProperty("provider.url" ));
}
// ----------------------------------------------------------
/**
* Gets the latest version of a feature from this FeatureDescriptor's provider
* @return The provider's FeatureDiscriptor
* @throws IOException
*/
public FeatureDescriptor providerVersion()
{
FeatureDescriptor latest = null;
try
{
FeatureProvider provider = provider();
if ( provider != null )
{
latest = isPlugin()
? provider.pluginDescriptor( name )
: provider.subsystemDescriptor( name );
}
}
catch (IOException e)
{
// just return null
}
return latest;
}
// ----------------------------------------------------------
/**
* Determine whether one descriptor's version is less than annother's.
* @param other the descriptor to compare against
* @return true if the current version for this subsystem is higher
* than the current version for the other descriptor
*/
public boolean isNewerThan( FeatureDescriptor other )
{
return ( other.versionMajor() < this.versionMajor() )
|| ( other.versionMajor() == this.versionMajor()
&& other.versionMinor() < this.versionMinor() )
|| ( other.versionMajor() == this.versionMajor()
&& other.versionMinor() == this.versionMinor()
&& other.versionRevision() < this.versionRevision() );
}
// ----------------------------------------------------------
/**
* Determine if a newer version of this subsystem is available from
* its provider.
* @return True if a newer version is available
* @throws IOException
*/
public boolean updateIsAvailable() throws IOException
{
boolean result = false;
FeatureDescriptor latest = providerVersion();
if ( latest != null )
{
result = latest.isNewerThan( this );
}
return result;
}
// ----------------------------------------------------------
/**
* Retrieve a subsystem-specific property's value.
* @param propName the name of the property to retrieve
* @return the value of the <i>name.propName</i> property, where
* <i>name</i> is the name of this subsystem
*/
public String getProperty( String propName )
{
return properties.getProperty( name + "." + propName );
}
// ----------------------------------------------------------
/**
* Retrieve a subsystem-specific property's value.
* @param propName the name of the property to retrieve
* @param defaultValue the value to use if the property is not found
* @return the value of the <i>name.propName</i> property, where
* <i>name</i> is the name of this subsystem, or the defaultValue
* if no such property is found
*/
public String getProperty( String propName, String defaultValue )
{
return properties.getProperty( name + "." + propName, defaultValue );
}
// ----------------------------------------------------------
/**
* Get the raw subsystem properties object to inspect lower-level
* information about this subsystem.
* @return the subsystem's properties
*/
public Properties properties()
{
return properties;
}
// ----------------------------------------------------------
/**
* Download this feature version from its provider.
* @param location the place to put the downloaded file
* @return null on success, or an error message on failure
*/
public String downloadTo(File location)
{
String result = null;
try
{
downloadTo(location, null);
}
catch (IOException e)
{
result = "An I/O exception occurred attempting to download "
+ "version "
+ currentVersion()
+ " of "
+ name()
+ ": "
+ e.getMessage();
}
return result;
}
// ----------------------------------------------------------
/**
* Download this feature version from its provider.
* @param location the place to put the downloaded file
* @param finalLocation to move the downloaded file to
* @throws IOException
*/
public void downloadTo(File location, File finalLocation)
throws IOException
{
InputStream in = null;
FileOutputStream out = null;
try
{
FeatureProvider provider = provider();
if (provider != null)
{
boolean crcpassed = true;
int i = 0;
String fileName = name + "_" + currentVersion() + ".jar";
String update = (isPlugin() ? "plugins/" : "subsystems/")
+ fileName;
File outFile = new File(location, fileName);
URL fileUrl = new URL(provider.url(), update);
if (outFile.length() == 0)
{
WCUpdater.logInfo("Downloading " + fileUrl + " to "
+ outFile.getAbsolutePath());
}
else
{
WCUpdater.logInfo("Resumed Downloading " + fileUrl
+ " to " + outFile.getAbsolutePath());
}
do
{
URLConnection connection = fileUrl.openConnection();
connection.setRequestProperty(
"Range", "bytes="+ outFile.length() +"-");
in = connection.getInputStream();
out = new FileOutputStream(outFile, true);
FileUtilities.copyStream(in, out);
out.close();
in.close();
//Perform checksum comparison
//Should delete file
if (!compareChecksums(outFile))
{
crcpassed = false;
outFile.delete();
}
i++;
}
while (!crcpassed && i < 5);
if (!crcpassed)
{
WCUpdater.logInfo("CRC mismatch between "
+ fileUrl
+ " and "
+ outFile.getAbsolutePath());
throw new IOException();
}
if (finalLocation != null && !finalLocation.equals(location))
{
//Moving from download location to final location
if (!outFile.renameTo(
new File(finalLocation, outFile.getName())))
{
WCUpdater.logError(getClass(),
"Unable to move file from "
+ location.getAbsolutePath()
+ " to "
+ finalLocation.getAbsolutePath());
}
else
{
WCUpdater.logInfo("Moved " + outFile.getName()
+ " to " + outFile.getAbsolutePath());
}
}
}
}
finally
{
if (out != null)
{
out.close();
}
if (in != null)
{
in.close();
}
}
}
// ----------------------------------------------------------
/**
* Download an updated version of this feature from its provider, if
* available.
* @param location the place to put the downloaded file
* @param finalLocation to move the downloaded file to
* @throws IOException
* @returns true if update was downloaded
*/
public boolean downloadUpdateIfNecessary(
File location, File finalLocation)
throws IOException
{
String updateAutomatically = getProperty( "updateAutomatically" );
if ( updateAutomatically != null
&& ( updateAutomatically.equals( "0" )
|| updateAutomatically.equals( "false" )
|| updateAutomatically.equals( "no" ) ) )
{
// If we shouldn't update this subsystem, skip it
return false;
}
FeatureDescriptor latest = providerVersion();
if(latest != null)
{
if (latest.isNewerThan( this ) )
{
WCUpdater.logInfo( "Updating " + name );
latest.downloadTo( location, finalLocation );
return true;
}
else if (name != null )
{
WCUpdater.logInfo( "Feature " + name + " is up to date." );
return false;
}
}
return false;
}
// ----------------------------------------------------------
/**
* Gets the current update status of this Feature.
* @returns The condition the update file is in.
*/
public Condition getUpdateStatus()
{
WCUpdater currentUpdater = WCUpdater.getInstance();
FeatureDescriptor providerVersion = providerVersion();
if (providerVersion == null)
{
return Condition.UNAVAILABLE;
}
else
{
return currentUpdater.getFileConditionFor(
name + "_" + providerVersion().currentVersion() + ".jar");
}
}
//~ Private Methods .....................................................
// ----------------------------------------------------------
/**
* Compares a downloaded checksum to a stored checksum.
* @param downloadedFile A file that has been downloaded
* @return True if the checksums match
*/
private boolean compareChecksums(File downloadedFile)
{
long checksum = FileUtilities.getCRCChecksum(downloadedFile);
long storedChecksum = longProperty("checksum");
if (storedChecksum != 0L && checksum != storedChecksum)
{
return false;
}
return true;
}
//~ Protected Methods .....................................................
// ----------------------------------------------------------
/**
* Access a given property and convert it to a numeric value (with a
* default of zero).
* @param propName the name of the property to look up
* @return the property's value as an int
*/
protected int intProperty( String propName )
{
int val = 0;
String str = getProperty( propName, "0" );
try {
if ( str.length() > 0 )
{
val = Integer.parseInt( str );
}
}
catch ( NumberFormatException e )
{
WCUpdater.logError( getClass(), "Non-numeric property " + propName
+ " for feature " + name );
}
return val;
}
// ----------------------------------------------------------
/**
* Access a given property and convert it to a numeric value (with a
* default of zero).
* @param propName the name of the property to look up
* @return the property's value as an long
*/
protected long longProperty( String propName )
{
long val = 0;
String str = getProperty( propName, "0" );
try {
if ( str.length() > 0 )
{
val = Long.parseLong( str );
}
}
catch ( NumberFormatException e )
{
WCUpdater.logError( getClass(), "Non-numeric property " + propName
+ " for feature " + name );
}
return val;
}
//~ Instance/static variables .............................................
protected Properties properties;
protected String name;
protected boolean isPlugin = false;
private String currentVersion;
private int versionMajor = -1;
private int versionMinor = -1;
private int versionRevision = -1;
}