/******************************************************************************* | |
* 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 ); | |
} | |
} | |
} |