blob: 1d2ffdfb3be764e056028bcd7e3a1ab0a33b5ef1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
******************************************************************************/
package org.eclipse.epp.wizard.installerbuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.log4j.Logger;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarOutputStream;
/**
* Cache for generated installers, uses the filesystem as a backing store
*
* @author mwoelker
*/
public class InstallerCache {
static Logger logger = Logger.getLogger( InstallerCache.class );
public static class Key {
@Override
public String toString() {
return "os=" + os + ";" + properties.toString();
}
public String getOs() {
return os;
}
public Properties getProperties() {
return properties;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( os == null )
? 0
: os.hashCode() );
result = prime
* result
+ ( ( properties == null )
? 0
: properties.hashCode() );
return result;
}
@Override
public boolean equals( Object obj ) {
if( this == obj )
return true;
if( obj == null )
return false;
if( getClass() != obj.getClass() )
return false;
Key other = ( Key )obj;
if( os == null ) {
if( other.os != null )
return false;
} else if( !os.equals( other.os ) )
return false;
if( properties == null ) {
if( other.properties != null )
return false;
} else if( !properties.equals( other.properties ) )
return false;
return true;
}
String os;
public Key( String os, Properties properties ) {
super();
this.os = os;
this.properties = properties;
}
Properties properties;
}
/**
* Entry in the cache, the actual installer file is created lazily, to reduce
* monitor contention on the cacheMap
*
* @author mwoelker
*/
public class Entry {
private Key key;
public Entry( Key key ) {
super();
this.key = key;
}
long acquisitionCount = 0;
public synchronized File getFile() {
if( file == null ) {
file = createInstaller( key );
}
return file;
}
File file;
boolean evicted = false;
boolean disposed = false;
public synchronized void acquire() {
acquisitionCount++;
}
public synchronized void release() {
acquisitionCount--;
if( acquisitionCount == 0 && evicted ) {
// Perform postponed eviction
logger.debug( "Evicting (actual): " + key );
deleteFile();
}
}
private void deleteFile() {
if( file != null ) {
file.delete();
}
}
public synchronized void evict() {
evicted = true;
if( acquisitionCount == 0 ) {
logger.debug( "Evicting directly: " + key );
deleteFile();
} else {
// File still in use, evict later
logger.debug( "Evicting (postponed): " + key );
}
}
}
protected class CacheMap extends LinkedHashMap<Key, Entry> {
private static final long serialVersionUID = 1L;
private final int capacity;
@Override
protected boolean removeEldestEntry( Map.Entry<Key, org.eclipse.epp.wizard.installerbuilder.InstallerCache.Entry> eldest )
{
boolean remove = false;
if( this.size() > this.capacity ) {
// remove Least recently used entry, and evict value from cache
remove = true;
eldest.getValue().evict();
}
return remove;
}
public CacheMap( int capacity, float loadFactor, boolean accessOrder ) {
super( capacity, loadFactor, accessOrder );
this.capacity = capacity;
}
}
final CacheMap cacheMap;
Configuration configuration = Activator.getDefault().getConfiguration();
private static final String INSTALLER_PROPERTIES_FILENAME = "installer.properties";
private static final String INSTALLER_TEMPLATE_PATH = "templates/installer";
private static final String PROPERTY_PLACEHOLDER_MARKER = "%?%";
private static final String ENCODING = "UTF-8";
private static final String TEMPFILE_EXTENSION = ".tmp";
private static final String TEMPFILE_NAME = "installer";
private static final int BUFFER = 1024;
public InstallerCache( int installerCacheSize ) {
cacheMap = new CacheMap( installerCacheSize, 1.75f, true );
}
public Entry getInstaller( Key key ) {
Entry entry;
synchronized( cacheMap ) {
entry = cacheMap.get( key );
if( entry == null ) {
entry = new Entry( key );
cacheMap.put( key, entry );
}
entry.acquire();
}
return entry;
}
protected File createInstaller( Key key ) {
File outFile = createTempFile();
File inFile = findRawInstallerFile( new File( configuration.getDownloadsDirectory(),
"templates/installer" ),
key.getOs() );
InputStream in = null;
OutputStream out = null;
try {
out = new FileOutputStream( outFile );
in = new FileInputStream( inFile );
String config = getInstallerConfiguration( key.getOs(),
key.getProperties() );
byte[] bytes = config.getBytes( ENCODING );
if( inFile.getName().endsWith( ".zip" ) ) {
ZipInputStream zis = null;
ZipOutputStream zos = null;
try {
zis = new ZipInputStream( in );
zos = new ZipOutputStream( out );
ZipEntry entry = zis.getNextEntry();
while( entry != null ) {
// Do not store directories explicitly
if( !entry.isDirectory() ) {
ZipEntry zipEntry = new ZipEntry( entry.getName() );
zos.putNextEntry( zipEntry );
copy( readSubStream( zis ), zos );
}
entry = zis.getNextEntry();
}
zos.putNextEntry( new ZipEntry( "eclipse/installer.properties" ) );
ByteArrayInputStream confStream = new ByteArrayInputStream( bytes );
copy( confStream, zos );
zos.closeEntry();
} catch( IOException e ) {
logger.error( "Could not create installer", e );
} finally {
tryClose( zis );
tryClose( zos );
}
} else if( inFile.getName().endsWith( ".tar.gz" ) ) {
GZIPInputStream gzis = null;
TarInputStream tis = null;
GZIPOutputStream gzos = null;
TarOutputStream tos = null;
try {
gzis = new GZIPInputStream( in );
tis = new TarInputStream( gzis );
gzos = new GZIPOutputStream( out );
tos = new TarOutputStream( gzos );
tos.setLongFileMode( TarOutputStream.LONGFILE_GNU );
TarEntry entry = tis.getNextEntry();
while( entry != null ) {
tos.putNextEntry( entry );
copy( readSubStream( tis ), tos );
tos.closeEntry();
entry = tis.getNextEntry();
}
String entryName = "eclipse/installer.properties";
//Bug (Beyhan): installer.properties under macosx should be also in in eclipse/ directory
// if( key.getOs().equals( "macosx" ) ) {
// entryName = "eclipse/p2installer.app/Contents/MacOS/installer.properties";
// }
TarEntry tarEntry = new TarEntry( entryName );
ByteArrayInputStream confStream = new ByteArrayInputStream( bytes );
tarEntry.setSize( bytes.length );
tos.putNextEntry( tarEntry );
copy( confStream, tos );
tos.closeEntry();
} catch( IOException e ) {
logger.error( "Could not create installer", e );
} finally {
tryClose( tis );
tryClose( gzis );
tryClose( tos );
tryClose( gzos );
}
}
} catch( IOException ioe ) {
logger.error( "Could not create installer", ioe );
} finally {
tryClose( in );
tryClose( out );
}
return outFile;
}
private File findRawInstallerFile( final File dir, final String os ) {
File result = null;
File[] files = dir.listFiles();
for( File found : files ) {
if( found.getName().indexOf( os ) > -1 ) {
result = found;
}
}
return result;
}
private String getInstallerConfiguration( String os, Properties p2Properties )
throws IOException // FIXME
{
String result = "";
File downloadDir = Activator.getDefault()
.getConfiguration()
.getDownloadsDirectory();
File templateDir = new File( downloadDir, INSTALLER_TEMPLATE_PATH );
File inFile = new File( templateDir, INSTALLER_PROPERTIES_FILENAME );
Properties installerProperties = new Properties();
installerProperties.load( new FileInputStream( inFile ) );
for( Object okey : installerProperties.keySet() ) {
String key = ( String )okey;
if( installerProperties.getProperty( key )
.equals( PROPERTY_PLACEHOLDER_MARKER ) )
{
String value = p2Properties.getProperty( key );
if( value != null ) {
installerProperties.setProperty( key, value );
} else {
throw new RuntimeException( "No value for placeholder parameter in request: "
+ key );
}
}
}
try {
ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
installerProperties.store( bufferStream, "" );
result = bufferStream.toString();
} catch( IOException e ) {
logger.error( "Could not create installer", e );
} finally {
}
return result;
}
private static InputStream readSubStream( final InputStream is )
throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy( is, baos );
return new ByteArrayInputStream( baos.toByteArray() );
}
private static void tryClose( final InputStream stream ) {
if( stream != null ) {
try {
stream.close();
} catch( IOException e ) {
logger.error( "Could not create installer", e );
}
}
}
private static void tryClose( final OutputStream stream ) {
if( stream != null ) {
try {
stream.close();
} catch( IOException e ) {
logger.error( "Could not create installer", e );
}
}
}
private File createTempFile() {
File downloadsDirectory = configuration.getTempDirectory();
File tempFile;
try {
tempFile = File.createTempFile( TEMPFILE_NAME,
TEMPFILE_EXTENSION,
downloadsDirectory );
} catch( IOException e ) {
throw new RuntimeException( "Unable to create installer temp file", e );
}
return tempFile;
}
private static void copy( final InputStream in, final OutputStream out )
throws IOException
{
byte[] data = new byte[ BUFFER ];
int currentByte = in.read( data, 0, BUFFER );
while( currentByte != -1 ) {
out.write( data, 0, currentByte );
currentByte = in.read( data, 0, BUFFER );
}
}
}