com.izforge.izpack.installer.data.UninstallDataWriter Maven / Gradle / Ivy
package com.izforge.izpack.installer.data;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.izforge.izpack.api.data.AutomatedInstallData;
import com.izforge.izpack.api.merge.Mergeable;
import com.izforge.izpack.api.rules.RulesEngine;
import com.izforge.izpack.data.CustomData;
import com.izforge.izpack.data.ExecutableFile;
import com.izforge.izpack.merge.resolve.PathResolver;
import com.izforge.izpack.util.IoHelper;
import com.izforge.izpack.util.file.FileUtils;
/**
* Writes uninstall data to an executable jar file.
*/
public class UninstallDataWriter
{
/**
* Uninstall data.
*/
private UninstallData uninstallData;
/**
* Install data.
*/
private AutomatedInstallData installData;
/**
* The path resolver.
*/
private PathResolver pathResolver;
/**
* The jar to write to.
*/
private JarOutputStream jar;
/**
* The underlying jar file stream.
*/
private FileOutputStream jarStream;
/**
* The rules engine.
*/
private RulesEngine rules;
/**
* The logger.
*/
private static final Logger logger = Logger.getLogger(UninstallDataWriter.class.getName());
/**
* Log file path variable name.
*/
private static final String LOGFILE_PATH = "InstallerFrame.logfilePath";
/**
* Constructs an UninstallDataWriter.
*
* @param uninstallData the uninstall data
* @param installData the install data
* @param pathResolver the path resolver
* @param rules the rules engine
*/
public UninstallDataWriter(UninstallData uninstallData, AutomatedInstallData installData, PathResolver pathResolver,
RulesEngine rules)
{
this.uninstallData = uninstallData;
this.installData = installData;
this.pathResolver = pathResolver;
this.rules = rules;
}
/**
* Determines if uninstall data should be written.
*
* Uninstall data should be written if {@link com.izforge.izpack.api.data.Info#getUninstallerCondition()} is
* empty, or evaluates true.
*
* @return true if uninstall data should be written, otherwise false
*/
public boolean isUninstallRequired()
{
String condition = installData.getInfo().getUninstallerCondition();
return (installData.getInfo().getUninstallerPath() != null)
&& (condition == null || condition.length() == 0 || rules.isConditionTrue(condition)
);
}
/**
* Writes the uninstall data.
*
* @return true if uninstall data was successfully written, otherwise false
*/
public boolean write()
{
boolean result = false;
try
{
BufferedWriter extLogWriter = getExternalLogFile();
createOutputJar();
System.out.println("[ Writing the uninstaller data ... ]");
writeJarSkeleton();
writeFilesLog(extLogWriter);
writeUninstallerJarFileLog();
writeExecutables();
writeUninstallerListeners();
writeJars();
writeNativeLibraries();
writeAdditionalUninstallData();
writeScriptFiles();
jar.close();
result = true;
}
catch (Throwable t)
{
logger.log(Level.SEVERE, t.getMessage(), t);
destroyJar(); // don't keep the jar - it may be incomplete or corrupted
}
return result;
}
/**
* Determines if an external log file should be written.
*
* @return the external log file writer, or null if none should be written or the file couldn't be created
*/
private BufferedWriter getExternalLogFile()
{
String logfile = installData.getVariable(LOGFILE_PATH);
BufferedWriter writer = null;
if (logfile != null)
{
if (logfile.toLowerCase().startsWith("default"))
{
logfile = installData.getInfo().getUninstallerPath() + "/install.log";
}
logfile = IoHelper.translatePath(logfile, installData.getVariables());
File outFile = new File(logfile);
if (!outFile.getParentFile().exists())
{
if (!outFile.getParentFile().mkdirs())
{
logger.warning("Failed to create directory: " + outFile.getParentFile().getPath());
}
}
FileOutputStream out = null;
try
{
out = new FileOutputStream(outFile);
}
catch (FileNotFoundException e)
{
logger.log(Level.WARNING, "Cannot create logfile", e);
}
if (out != null)
{
writer = new BufferedWriter(new OutputStreamWriter(out));
}
}
return writer;
}
/**
* Writes the uninstaller skeleton.
*
* @throws IOException for any I/O error
* @throws com.izforge.izpack.api.exception.IzPackException
* for any IzPack error
*/
private void writeJarSkeleton() throws IOException
{
List uninstallerMerge = pathResolver.getMergeableFromPath("com/izforge/izpack/uninstaller/");
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("uninstaller-META-INF/", "META-INF/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/api/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/data/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/core/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/util/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/gui/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/img/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("org/picocontainer/"));
// indirectly required by Librarian, which pulls in IoHelper. TODO
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("org/apache/tools/zip/"));
//required by console uninstaller
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("jline/console/completer/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("jline/internal/"));
if (!uninstallData.getUninstallerListeners().isEmpty())
{
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/event/"));
}
if (rules.isConditionTrue("izpack.windowsinstall"))
{
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/izforge/izpack/core/os/"));
uninstallerMerge.addAll(pathResolver.getMergeableFromPath("com/coi/tools/os/"));
}
for (Mergeable mergeable : uninstallerMerge)
{
mergeable.merge(jar);
}
if (installData.getInfo().isPrivilegedExecutionRequiredUninstaller())
{
boolean shouldElevate = true;
String conditionId = installData.getInfo().getPrivilegedExecutionConditionID();
if (conditionId != null)
{
// only elevate permissions when condition is true
shouldElevate = rules.isConditionTrue(conditionId);
}
if (shouldElevate)
{
// Add resources required to elevate privileges
jar.putNextEntry(new JarEntry("exec-admin"));
jar.closeEntry();
if (rules.isConditionTrue("izpack.windowsinstall"))
{
writeResource("com/izforge/izpack/util/windows/elevate.js");
}
if (rules.isConditionTrue("izpack.macinstall"))
{
writeResource("com/izforge/izpack/util/mac/run-with-privileges-on-osx");
}
}
}
// We put the langpack
List langPack = pathResolver.getMergeableFromPath("resources/langpacks/"
+ installData.getLocaleISO3() + ".xml",
"langpack.xml");
for (Mergeable mergeable : langPack)
{
mergeable.merge(jar);
}
}
/**
* Writes the file log.
*
* @param extLogWriter the external log writer. May be null
* @throws IOException for any I/O error
*/
private void writeFilesLog(BufferedWriter extLogWriter) throws IOException
{
List files = uninstallData.getUninstalableFilesList();
jar.putNextEntry(new JarEntry("install.log"));
BufferedWriter logWriter = new BufferedWriter(new OutputStreamWriter(jar));
logWriter.write(installData.getInstallPath());
logWriter.newLine();
Iterator iter = files.iterator();
if (extLogWriter != null)
{
// Write intern (in uninstaller.jar) and extern log file.
while (iter.hasNext())
{
String txt = iter.next();
logWriter.write(txt);
extLogWriter.write(txt);
if (iter.hasNext())
{
logWriter.newLine();
extLogWriter.newLine();
}
}
logWriter.flush();
extLogWriter.flush();
extLogWriter.close();
}
else
{
while (iter.hasNext())
{
String txt = iter.next();
logWriter.write(txt);
if (iter.hasNext())
{
logWriter.newLine();
}
}
logWriter.flush();
}
jar.closeEntry();
}
/**
* Writes executables to execute on uninstall.
*
* @throws IOException for any I/O error
*/
private void writeExecutables() throws IOException
{
jar.putNextEntry(new JarEntry("executables"));
ObjectOutputStream execStream = new ObjectOutputStream(jar);
execStream.writeInt(uninstallData.getExecutablesList().size());
for (ExecutableFile file : uninstallData.getExecutablesList())
{
execStream.writeObject(file);
}
execStream.flush();
jar.closeEntry();
}
/**
* Writes the uinstaller jar file log.
*
* @throws IOException for any I/O error
*/
private void writeUninstallerJarFileLog() throws IOException
{
BufferedWriter logWriter;
jar.putNextEntry(new JarEntry("jarlocation.log"));
logWriter = new BufferedWriter(new OutputStreamWriter(jar));
logWriter.write(uninstallData.getUninstallerJarFilename());
logWriter.newLine();
logWriter.write(uninstallData.getUninstallerPath());
logWriter.flush();
jar.closeEntry();
}
/**
* Writes uninstaller listeners.
*
* @throws IOException for any I/O error
*/
private void writeUninstallerListeners() throws IOException
{
ArrayList listeners = new ArrayList();
writeCustomDataResources(uninstallData.getUninstallerListeners());
for (CustomData data : uninstallData.getUninstallerListeners())
{
if (data.listenerName != null)
{
listeners.add(data.listenerName);
}
}
jar.putNextEntry(new JarEntry("uninstallerListeners"));
ObjectOutputStream stream = new ObjectOutputStream(jar);
stream.writeObject(listeners);
stream.flush();
jar.closeEntry();
}
/**
* Writes uninstaller jars.
*
* @throws IOException for any I/O error
*/
private void writeJars() throws IOException
{
writeCustomDataResources(uninstallData.getJars());
}
/**
* Writes native libraries.
*
* @throws IOException for any I/O error
*/
private void writeNativeLibraries() throws IOException
{
for (String path : uninstallData.getNativeLibraries())
{
writeResource(path);
}
}
/**
* Writes data from {@link com.izforge.izpack.installer.data.UninstallData#getAdditionalData()}.
*
* This silently ignores failures to locate custom resources, logging it instead.
*
* @throws IOException for any I/O error
*/
private void writeAdditionalUninstallData() throws IOException
{
Map additionalData = uninstallData.getAdditionalData();
if (additionalData != null && !additionalData.isEmpty())
{
for (String key : additionalData.keySet())
{
Object content = additionalData.get(key);
writeContent(key, content);
}
}
}
/**
* Writes uninstall scripts.
*
* @throws IOException for any I/O error
*/
private void writeScriptFiles() throws IOException
{
ArrayList unInstallScripts = uninstallData.getUninstallScripts();
ObjectOutputStream rootStream;
int idx = 0;
for (String unInstallScript : unInstallScripts)
{
jar.putNextEntry(new JarEntry(UninstallData.ROOTSCRIPT + Integer.toString(idx)));
rootStream = new ObjectOutputStream(jar);
rootStream.writeUTF(unInstallScript);
rootStream.flush();
jar.closeEntry();
idx++;
}
}
/**
* Writes the resources referenced by {@link CustomData#contents}.
*
* @param customData the custom data to write
* @throws IOException for any I/O error
*/
private void writeCustomDataResources(List customData) throws IOException
{
for (CustomData data : customData)
{
if (data.contents != null)
{
for (String content : data.contents)
{
writeResource(content);
}
}
}
}
/**
* Writes a resource to the jar, if it is not already present.
*
* @param path the resource path
* @throws IOException for any I/O error
*/
private void writeResource(String path) throws IOException
{
for (Mergeable mergeable : pathResolver.getMergeableFromPath(path))
{
mergeable.merge(jar);
}
}
/**
* Writes content to the jar.
*
* @param path the path to write to
* @param content the content to write. May be an object or a ByteArrayOutputStream. No idea why for the latter. TODO
* @throws IOException for any I/O error
*/
private void writeContent(String path, Object content) throws IOException
{
jar.putNextEntry(new JarEntry(path));
if (content instanceof ByteArrayOutputStream)
{
((ByteArrayOutputStream) content).writeTo(jar);
}
else
{
ObjectOutputStream out = new ObjectOutputStream(jar);
out.writeObject(content);
out.flush();
}
jar.closeEntry();
}
/**
* Creates the uninstaller jar file.
*
* @throws IOException for any I/O error
*/
private void createOutputJar() throws IOException
{
// Create the uninstaller directory
String dirPath = IoHelper.translatePath(installData.getInfo().getUninstallerPath(), installData.getVariables());
String jarPath = dirPath + File.separator + installData.getInfo().getUninstallerName();
File dir = new File(dirPath);
if (!dir.exists() && !dir.mkdirs())
{
throw new IOException("Failed to create output path: " + dir);
}
// Log the uninstaller deletion information
uninstallData.setUninstallerJarFilename(jarPath);
uninstallData.setUninstallerPath(dirPath);
// Create the jar file
jarStream = new FileOutputStream(jarPath);
jar = new JarOutputStream(new BufferedOutputStream(jarStream));
jar.setLevel(9);
uninstallData.addFile(jarPath, true);
}
/**
* Destroys the uninstaller jar when it cannot be written.
*/
private void destroyJar()
{
FileUtils.close(jar);
FileUtils.close(jarStream); // if jar cannot be closed, then need to close underlying stream
String path = uninstallData.getUninstallerJarFilename();
if (path != null)
{
File file = new File(path);
if (file.exists() && !file.delete())
{
logger.warning("Failed to delete incomplete uninstall information: " + path);
}
}
}
}