/***************************************************************************** * Copyright (c) 2006-2007, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. *****************************************************************************/ package org.eclipse.buckminster.jnlp.p2.bootstrap; import static org.eclipse.buckminster.jnlp.p2.bootstrap.BootstrapConstants.ERROR_CODE_FILE_IO_EXCEPTION; import static org.eclipse.buckminster.jnlp.p2.bootstrap.BootstrapConstants.ERROR_CODE_MALFORMED_PROPERTY_EXCEPTION; import static org.eclipse.buckminster.jnlp.p2.bootstrap.BootstrapConstants.ERROR_CODE_MATERIALIZER_INSTALL_EXCEPTION; import static org.eclipse.buckminster.jnlp.p2.bootstrap.BootstrapConstants.ERROR_CODE_PROPERTY_IO_EXCEPTION; import static org.eclipse.buckminster.jnlp.p2.bootstrap.BootstrapConstants.ERROR_CODE_REMOTE_IO_EXCEPTION; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Enumeration; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Pack200; import java.util.jar.Pack200.Unpacker; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class DirectorInstaller { private static final String DIRECTOR_FOLDER_NAME = "director"; //$NON-NLS-1$ private static final String DIRECTOR_BUILD_PROPERTIES_FILE_NAME = "director.build.properties"; private static final String PROP_BUILD_TIMESTAMP = "build.timestamp"; private static final String PACK_PROPERTIES_FILE = "pack.properties"; //$NON-NLS-1$ private static final String PACK_SUFFIX = ".pack.gz"; //$NON-NLS-1$ private static final int PACK_SUFFIX_LEN = PACK_SUFFIX.length(); private static final char s_fileSep; static { String fileSep = System.getProperty("file.separator"); //$NON-NLS-1$ s_fileSep = (fileSep == null || fileSep.length() < 1) ? '/' : fileSep.charAt(0); } public static long copyFile(InputStream input, OutputStream output) throws IOException { byte[] buf = new byte[0x2000]; long total = 0; int count; while((count = input.read(buf)) > 0) { output.write(buf, 0, count); total += count; } return total; } private static String encrypt(byte[] bytes, String algorithmName) { String md5val = ""; //$NON-NLS-1$ MessageDigest algorithm = null; try { algorithm = MessageDigest.getInstance(algorithmName); } catch(NoSuchAlgorithmException nsae) { throw new IllegalArgumentException(Messages.getString("unknown_encrypt_algorithm_colon") + algorithmName); //$NON-NLS-1$ } algorithm.reset(); algorithm.update(bytes); byte messageDigest[] = algorithm.digest(); StringBuffer hexString = new StringBuffer(); for(int i = 0; i < messageDigest.length; i++) { String hex = Integer.toHexString(0xFF & messageDigest[i]); if(hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } md5val = hexString.toString(); return md5val; } private static String osAdjustName(String name) { if(s_fileSep != '/') name = name.replace('/', s_fileSep); return name; } private static void renameFile(File from, File to) { if(!from.renameTo(to)) throw new RuntimeException(Messages.getString("unable_to_rename_0_to_1", from, to)); //$NON-NLS-1$ } private File m_installLocation; private File m_directorFolder; private Unpacker m_unpacker; public DirectorInstaller(File installLocation) { m_installLocation = installLocation; m_directorFolder = new File(m_installLocation, DIRECTOR_FOLDER_NAME); } public File getDirectorFolder() { return m_directorFolder; } public String getDirectorFolderName() { return DIRECTOR_FOLDER_NAME; } public String[] getInstallFolders() { return new String[] { DIRECTOR_FOLDER_NAME }; } public void installDirector(String directorArchiveURL, String directorBuildPropertiesURL, ProgressFacade monitor) throws JNLPException, OperationCanceledException, CorruptedFileException { monitor.setTask(Messages.getString("Installing director application"), 100); //$NON-NLS-1$ try { removeDirector(); monitor.taskIncrementalProgress(10); installResource(directorArchiveURL, monitor); //$NON-NLS-1$ monitor.checkCanceled(); // copy director.build.properties file try { File directorBuildPropertiesFile = new File(m_directorFolder, DIRECTOR_BUILD_PROPERTIES_FILE_NAME); directorBuildPropertiesFile.createNewFile(); InputStream is = new URL(directorBuildPropertiesURL).openStream(); OutputStream os = new FileOutputStream(directorBuildPropertiesFile); try { copyFile(is, os); } finally { Utils.close(os); Utils.close(is); } } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_create_a_new_file"), //$NON-NLS-1$ Messages.getString("check_disk_space_system_permissions_and_try_again"), ERROR_CODE_FILE_IO_EXCEPTION, e, false); //$NON-NLS-1$ } } finally { monitor.taskDone(); } } public boolean isLatestDirectorInstalled(String remoteDirectorBuildPropertiesURL) throws JNLPException { Properties remoteBuildProp = loadProperties(remoteDirectorBuildPropertiesURL); File directorBuildPropertiesFile = new File(m_directorFolder, DIRECTOR_BUILD_PROPERTIES_FILE_NAME); if(m_directorFolder.exists()) { if(directorBuildPropertiesFile.exists()) { Properties localBuildProp = loadProperties(directorBuildPropertiesFile.toURI().toString()); String remoteTimestamp = (String)remoteBuildProp.get(PROP_BUILD_TIMESTAMP); String localTimestamp = (String)localBuildProp.get(PROP_BUILD_TIMESTAMP); if(remoteTimestamp != null && localTimestamp != null && remoteTimestamp.compareTo(localTimestamp) <= 0) { // local director is up-to-date return true; } } } return false; } public void removeDirector() throws JNLPException { Utils.deleteRecursive(m_directorFolder); } private String[] getSkipFiles() { return new String[] { PACK_PROPERTIES_FILE }; } private void installFromStream(InputStream productZip, InputStream productZipMD5, ProgressFacade monitor) throws JNLPException, OperationCanceledException, CorruptedFileException, IOException { byte[] productBytes = readStream(productZip); String computedMD5 = encrypt(productBytes, "MD5").trim(); //$NON-NLS-1$ String originalMD5 = new String(readStream(productZipMD5)).trim(); if(!computedMD5.equals(originalMD5)) throw new CorruptedFileException(); ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(productBytes)); ZipEntry zipEntry; zipEntryCycle: while((zipEntry = zipInput.getNextEntry()) != null) { monitor.checkCanceled(); for(String skipFile : getSkipFiles()) if(zipEntry.getName().equals(skipFile)) continue zipEntryCycle; if(!zipEntry.getName().startsWith(getDirectorFolderName())) { throw new JNLPException( Messages.getString("director_application_is_probably_corrupted"), Messages.getString("report_the_problem"), ERROR_CODE_MATERIALIZER_INSTALL_EXCEPTION); //$NON-NLS-1$ //$NON-NLS-2$ } String name = osAdjustName(zipEntry.getName()); File file; if(zipEntry.isDirectory()) { file = new File(m_installLocation, name); file.mkdirs(); } else { if(name.endsWith(PACK_SUFFIX)) { // Pack200 compressed file // file = new File(m_installLocation, name.substring(0, name.length() - PACK_SUFFIX_LEN)); OutputStream output; try { output = new FileOutputStream(file); } catch(FileNotFoundException e) { throw new JNLPException( Messages.getString("can_not_create_file_colon") + file.toString(), //$NON-NLS-1$ Messages.getString("check_disk_space_system_permissions_and_try_again"), ERROR_CODE_FILE_IO_EXCEPTION, //$NON-NLS-1$ e, false); } try { try { storeUnpacked(zipInput, output); } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_unzip_and_save_to_file_colon") + file.toString(), //$NON-NLS-1$ Messages.getString("check_disk_space_system_permissions_and_try_again"), //$NON-NLS-1$ ERROR_CODE_FILE_IO_EXCEPTION, e, false); } monitor.taskIncrementalProgress(1); } finally { Utils.close(output); } } else { // This is so quick that it doesn't generate a progress tick // file = new File(m_installLocation, name); try { storeVerbatim(file, zipInput); } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_save_to_file_colon") + file.toString(), //$NON-NLS-1$ Messages.getString("check_disk_space_system_permissions_and_try_again"), ERROR_CODE_FILE_IO_EXCEPTION, //$NON-NLS-1$ e, false); } } if(file.getName().endsWith(".jar")) //$NON-NLS-1$ { try { if(recursiveUnpack(file)) monitor.taskIncrementalProgress(1); } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_unpack_file_colon") + file.toString(), //$NON-NLS-1$ Messages.getString("check_disk_space_system_permissions_and_try_again"), ERROR_CODE_FILE_IO_EXCEPTION, //$NON-NLS-1$ e); } } } long tz = zipEntry.getTime(); if(tz != -1L) file.setLastModified(tz); } } private void installResource(String resourceURL, ProgressFacade monitor) throws JNLPException, OperationCanceledException, CorruptedFileException { try { monitor.taskIncrementalProgress(5); InputStream resourceZip = new URL(resourceURL).openStream(); InputStream resourceZipMD5 = new URL(resourceURL + ".MD5").openStream();; //$NON-NLS-1$ monitor.taskIncrementalProgress(5); if(resourceZip == null) { // // Nothing to install // throw new RuntimeException(Messages.getString("missing_0_resource", resourceURL)); //$NON-NLS-1$ } try { installFromStream(resourceZip, resourceZipMD5, monitor); } finally { Utils.close(resourceZip); Utils.close(resourceZipMD5); } } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_read_materialization_wizard_resource"), //$NON-NLS-1$ Messages.getString("check_your_internet_connection_and_try_again"), ERROR_CODE_REMOTE_IO_EXCEPTION, e); //$NON-NLS-1$ } } private Properties loadProperties(String urlString) throws JNLPException { Properties prop = new Properties(); URL propertiesURL = null; InputStream is = null; try { propertiesURL = new URL(urlString.trim()); is = propertiesURL.openStream(); prop.load(is); } catch(MalformedURLException e) { throw new JNLPException( Messages.getString("can_not_read_URL_to_properties"), Messages.getString("report_the_problem"), //$NON-NLS-1$ //$NON-NLS-2$ ERROR_CODE_MALFORMED_PROPERTY_EXCEPTION, e); } catch(IOException e) { throw new JNLPException( Messages.getString("can_not_read_materialization_properties"), Messages.getString("try_again_and_if_the_problem_persists_please_report_the_problem"), //$NON-NLS-1$ //$NON-NLS-2$ ERROR_CODE_PROPERTY_IO_EXCEPTION, e); } finally { Utils.close(is); } return prop; } private byte[] readStream(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] copyBuf = new byte[8192]; int count; while((count = input.read(copyBuf)) > 0) output.write(copyBuf, 0, count); return output.toByteArray(); } private boolean recursiveUnpack(File file) throws IOException { // Make sure this jar file doesn't contain packed entries // JarOutputStream jarOutput = null; JarFile jarFile = new JarFile(file); File tempFile = null; try { boolean needRepack = false; Enumeration<JarEntry> entries = jarFile.entries(); while(entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if(entry.getName().endsWith(PACK_SUFFIX)) { needRepack = true; break; } } if(!needRepack) return false; // We need to repack this jar // byte[] copyBuf = new byte[8192]; tempFile = File.createTempFile("unpack", ".jar", file.getParentFile()); //$NON-NLS-1$ //$NON-NLS-2$ jarOutput = new JarOutputStream(new FileOutputStream(tempFile), jarFile.getManifest()); entries = jarFile.entries(); while(entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); if(entry.isDirectory()) { if(!entryName.equalsIgnoreCase("meta-inf")) //$NON-NLS-1$ // // It's there already. // jarOutput.putNextEntry(entry); continue; } if(entryName.equalsIgnoreCase("meta-inf/manifest.mf")) //$NON-NLS-1$ // // Manifest is already written to output // continue; InputStream input = jarFile.getInputStream(entry); try { if(entryName.endsWith(PACK_SUFFIX)) { String unpackedName = entryName.substring(0, entryName.length() - PACK_SUFFIX_LEN); JarEntry unpackedEntry = new JarEntry(unpackedName); unpackedEntry.setMethod(ZipEntry.DEFLATED); jarOutput.putNextEntry(unpackedEntry); storeUnpacked(input, jarOutput); } else { jarOutput.putNextEntry(entry); int count; while((count = input.read(copyBuf)) > 0) jarOutput.write(copyBuf, 0, count); } } finally { Utils.close(input); } } } finally { Utils.close(jarOutput); jarFile.close(); } File mvTemp = new File(file.getAbsolutePath() + ".move"); //$NON-NLS-1$ mvTemp.delete(); renameFile(file, mvTemp); renameFile(tempFile, file); mvTemp.delete(); return true; } private synchronized void storeUnpacked(InputStream packedInput, OutputStream result) throws IOException { if(m_unpacker == null) m_unpacker = Pack200.newUnpacker(); GZIPInputStream gzipInput = null; try { // Wrap the incoming stream to prevent it from being closed // when the GZIPInputStream is closed. // gzipInput = new GZIPInputStream(new FilterInputStream(packedInput) { @Override public void close() { } }); JarOutputStream jarOut = new JarOutputStream(result); m_unpacker.unpack(gzipInput, jarOut); gzipInput = null; // Closed by unpack jarOut.finish(); } finally { Utils.close(gzipInput); } } private synchronized void storeVerbatim(File file, InputStream packedInput) throws IOException { OutputStream out = null; try { int count; byte[] buffer = new byte[0x1000]; out = new FileOutputStream(file); while((count = packedInput.read(buffer)) > 0) out.write(buffer, 0, count); } finally { Utils.close(out); } } }