/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.outfit.shell;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.MissingResourceException;
import java.util.regex.Pattern;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.apache.commons.io.FileUtils;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
/**
* Installs the jar of the {@link org.novelang.outfit.shell.insider.InsiderAgent} somewhere
* on the local filesystem.
* The JVM installs agents from plain jar files.
* <p>
* This class applies the
* <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom">Initialization on demand idiom</a>.
*
* @author Laurent Caillette
*/
public class AgentFileInstaller {
private static final Logger LOGGER = LoggerFactory.getLogger( AgentFileInstaller.class ) ;
/**
* Name of the system property to set the agent jar file externally.
* This is useful when running tests from an IDE.
* The "{@value #VERSION_PLACEHOLDER}" substring evaluates to
*/
public static final String AGENTJARFILE_SYSTEMPROPERTYNAME =
"org.novelang.outfit.shell.agentjarfile" ;
private static final String VERSION_PLACEHOLDER = "${project.version}" ;
private static final String DEFAULT_VERSION = "SNAPSHOT" ;
/**
* Name of the system property to set the version overriding the content of the
* {@value #VERSION_RESOURCE_NAME} resource.
*/
public static final String VERSIONOVERRIDE_SYSTEMPROPERTYNAME =
"org.novelang.outfit.shell.versionoverride" ;
/**
* Version of the embedded jar. Needed to find its resource name.
*/
private final String version ;
private final File jarFile ;
public static final String VERSION_PLACEHOLDER_REGEX = Pattern.quote( VERSION_PLACEHOLDER );
public static boolean mayHaveValidInstance() {
return
System.getProperty( VERSIONOVERRIDE_SYSTEMPROPERTYNAME ) != null
|| AgentFileInstaller.class.getResource( VERSION_RESOURCE_NAME ) != null
;
}
private AgentFileInstaller() throws IOException {
final String versionOverride = System.getProperty( VERSIONOVERRIDE_SYSTEMPROPERTYNAME ) ;
if( versionOverride == null ) {
final URL versionResource = AgentFileInstaller.class.getResource( VERSION_RESOURCE_NAME ) ;
if( versionResource == null ) {
throw new IllegalStateException( "Couldn't find resource for '" + VERSION_RESOURCE_NAME + "' " +
"(when building with Maven, this may be caused by Insider jar not present " +
"in the repository for current version)"
) ;
}
version = Resources.toString( versionResource, Charsets.UTF_8 ) ;
LOGGER.info( "Using version '", version, "' as found inside ",
"'", VERSION_RESOURCE_NAME, "' resource." ) ;
} else {
version = versionOverride ;
LOGGER.info( "Using version override '", version, "' from system property ",
"'", VERSIONOVERRIDE_SYSTEMPROPERTYNAME, "'." ) ;
}
final String jarFileNameFromSystemProperty =
System.getProperty( AGENTJARFILE_SYSTEMPROPERTYNAME ) ;
if( jarFileNameFromSystemProperty == null ) {
try {
jarFile = File.createTempFile( "Novelang-insider-agent", ".jar" ).getCanonicalFile() ;
copyResourceToFile( JAR_RESOURCE_NAME_RADIX + version + ".jar", jarFile ) ;
LOGGER.info( "Using jar file '", jarFile.getAbsolutePath(), "'." ) ;
} catch( IOException e ) {
throw new RuntimeException( e ) ;
}
} else {
jarFile = resolveWithVersion( jarFileNameFromSystemProperty ) ;
if( ! jarFile.isFile() ) {
throw new IllegalArgumentException(
"Jar file '" + jarFile.getAbsolutePath() + "' doesn't exist as a file" ) ;
}
LOGGER.info( "Using jar file '", jarFile.getAbsolutePath(), "' ",
"set from system property '", AGENTJARFILE_SYSTEMPROPERTYNAME, "'." ) ;
}
}
public File resolveWithVersion( final String filenameWithVersionPlaceholders ) {
final String resolvedJarFileName = filenameWithVersionPlaceholders.replaceAll(
VERSION_PLACEHOLDER_REGEX, version ) ;
final File resolvedFile = new File( resolvedJarFileName );
return resolvedFile;
}
/**
* Returns a {@code File} object referencing the jar containing the
* {@link org.novelang.outfit.shell.insider.InsiderAgent}.
*
* @return a non-null object.
*/
public File getJarFile() {
return jarFile ;
}
public void copyVersionedJarToFile( final String jarResourceRadix, final File file )
throws IOException
{
copyResourceToFile( jarResourceRadix + version + ".jar", file ) ;
}
public static void copyResourceToFile( final String resourceName, final File file )
throws IOException {
final URL resourceUrl = AgentFileInstaller.class.getResource( resourceName ) ;
if( resourceUrl == null ) {
throw new MissingResourceException(
"The resource '" + resourceName +"' does not appear in the classpath " +
"(Maven should handle this, IDEs probably won't)",
AgentFileInstaller.class.getName(),
resourceName
) ;
}
LOGGER.info( "Copying resource '", resourceName, "' to ",
"'", file.getAbsolutePath(), "'" ) ;
FileUtils.copyURLToFile( resourceUrl, file ) ;
}
/**
* Defined by the assembly of the Insider project.
*/
@SuppressWarnings( { "HardcodedFileSeparator" } )
private static final String JAR_RESOURCE_NAME_RADIX =
"/org/novelang/outfit/shell/Novelang-insider-" ;
/**
* Defined by the assembly of the Insider project.
*/
@SuppressWarnings( { "HardcodedFileSeparator" } )
private static final String VERSION_RESOURCE_NAME=
"/org/novelang/outfit/shell/version.txt" ;
// ========================
// Initialization on demand
// ========================
@SuppressWarnings( { "UtilityClassWithoutPrivateConstructor" } )
private static class LazyHolder {
private static final AgentFileInstaller INSTANCE ;
static {
try {
INSTANCE = new AgentFileInstaller() ;
} catch( IOException e ) {
throw new RuntimeException( e ) ;
}
}
}
public static AgentFileInstaller getInstance() {
return LazyHolder.INSTANCE ;
}
}