/*
* IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
*
* http://izpack.org/
* http://izpack.codehaus.org/
*
* Copyright 2004 Klaus Bartz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.izforge.izpack.event;
import com.coi.tools.os.win.NativeLibException;
import com.izforge.izpack.Pack;
import com.izforge.izpack.installer.AutomatedInstallData;
import com.izforge.izpack.installer.UninstallData;
import com.izforge.izpack.installer.Unpacker;
import com.izforge.izpack.util.AbstractUIProgressHandler;
import com.izforge.izpack.util.SpecHelper;
import com.izforge.izpack.util.VariableSubstitutor;
import com.izforge.izpack.util.os.RegistryDefaultHandler;
import com.izforge.izpack.util.os.RegistryHandler;
import com.izforge.izpack.util.os.WrappedNativeLibException;
import com.izforge.izpack.adaptator.IXMLElement;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* Installer custom action for handling registry entries on Windows. On Unix nothing will be done.
* The actions which should be performed are defined in a resource file named "RegistrySpec.xml".
* This resource should be declared in the installation definition file (install.xml), else an
* exception will be raised during execution of this custom action. The related DTD is
* appl/install/IzPack/resources/registry.dtd.
*
* @author Klaus Bartz
*/
public class RegistryInstallerListener extends NativeInstallerListener
{
/**
* The name of the XML file that specifies the registry entries.
*/
private static final String SPEC_FILE_NAME = "RegistrySpec.xml";
private static final String REG_KEY = "key";
private static final String REG_VALUE = "value";
private static final String REG_ROOT = "root";
private static final String REG_BASENAME = "name";
private static final String REG_KEYPATH = "keypath";
private static final String REG_DWORD = "dword";
private static final String REG_STRING = "string";
private static final String REG_MULTI = "multi";
private static final String REG_BIN = "bin";
private static final String REG_DATA = "data";
private static final String REG_OVERRIDE = "override";
private static final String SAVE_PREVIOUS = "saveprevious";
/**
* Default constructor.
*/
public RegistryInstallerListener()
{
super(true);
}
/*
* (non-Javadoc)
*
* @see com.izforge.izpack.compiler.InstallerListener#beforePacks(com.izforge.izpack.installer.AutomatedInstallData,
* int, com.izforge.izpack.util.AbstractUIProgressHandler)
*/
public void beforePacks(AutomatedInstallData idata, Integer npacks,
AbstractUIProgressHandler handler) throws Exception
{
super.beforePacks(idata, npacks, handler);
initializeRegistryHandler(idata);
}
/*
* (non-Javadoc)
*
* @see com.izforge.izpack.compiler.InstallerListener#afterPacks(com.izforge.izpack.installer.AutomatedInstallData,
* com.izforge.izpack.util.AbstractUIProgressHandler)
*/
public void afterPacks(AutomatedInstallData idata, AbstractUIProgressHandler handler)
throws Exception
{
try
{
// Start logging
RegistryHandler rh = RegistryDefaultHandler.getInstance();
if (rh == null)
{
return;
}
IXMLElement uninstallerPack = null;
// No interrupt desired after writing registry entries.
Unpacker.setDiscardInterrupt(true);
rh.activateLogging();
if (getSpecHelper().getSpec() != null)
{
VariableSubstitutor substitutor = new VariableSubstitutor(idata.getVariables());
Iterator iter = idata.selectedPacks.iterator();
// Get the special pack "UninstallStuff" which contains values
// for the uninstaller entry.
uninstallerPack = getSpecHelper().getPackForName("UninstallStuff");
performPack(uninstallerPack, substitutor);
// Now perform the selected packs.
while (iter != null && iter.hasNext())
{
// Resolve data for current pack.
IXMLElement pack = getSpecHelper().getPackForName(((Pack) iter.next()).name);
performPack(pack, substitutor);
}
}
String uninstallSuffix = idata.getVariable("UninstallKeySuffix");
if (uninstallSuffix != null)
{
rh.setUninstallName(rh.getUninstallName() + " " + uninstallSuffix);
}
// Generate uninstaller key automatically if not defined in spec.
if (uninstallerPack == null)
{
rh.registerUninstallKey();
}
// Get the logging info from the registry class and put it into
// the uninstaller. The RegistryUninstallerListener loads that data
// and rewind the made entries.
// This is the common way to transport informations from an
// installer CustomAction to the corresponding uninstaller
// CustomAction.
List<Object> info = rh.getLoggingInfo();
if (info != null)
{
UninstallData.getInstance().addAdditionalData("registryEntries", info);
}
}
catch (Exception e)
{
if (e instanceof NativeLibException)
{
throw new WrappedNativeLibException(e);
}
else
{
throw e;
}
}
}
/**
* Performs the registry settings for the given pack.
*
* @param pack XML elemtent which contains the registry settings for one pack
* @throws Exception
*/
private void performPack(IXMLElement pack, VariableSubstitutor substitutor) throws Exception
{
if (pack == null)
{
return;
}
// Get all entries for registry settings.
Vector regEntries = pack.getChildren();
if (regEntries == null)
{
return;
}
Iterator entriesIter = regEntries.iterator();
while (entriesIter != null && entriesIter.hasNext())
{
IXMLElement regEntry = (IXMLElement) entriesIter.next();
// Perform one registry entry.
String type = regEntry.getName();
if (type.equalsIgnoreCase(REG_KEY))
{
performKeySetting(regEntry, substitutor);
}
else if (type.equalsIgnoreCase(REG_VALUE))
{
performValueSetting(regEntry, substitutor);
}
else
// No valid type.
{
getSpecHelper().parseError(regEntry,
"Non-valid type of entry; only 'key' and 'value' are allowed.");
}
}
}
/**
* Perform the setting of one value.
*
* @param regEntry element which contains the description of the value to be set
* @param substitutor variable substitutor to be used for revising the regEntry contents
*/
private void performValueSetting(IXMLElement regEntry, VariableSubstitutor substitutor)
throws Exception
{
SpecHelper specHelper = getSpecHelper();
String name = specHelper.getRequiredAttribute(regEntry, REG_BASENAME);
name = substitutor.substitute(name, null);
String keypath = specHelper.getRequiredAttribute(regEntry, REG_KEYPATH);
keypath = substitutor.substitute(keypath, null);
String root = specHelper.getRequiredAttribute(regEntry, REG_ROOT);
int rootId = resolveRoot(regEntry, root, substitutor);
RegistryHandler rh = RegistryDefaultHandler.getInstance();
if (rh == null)
{
return;
}
rh.setRoot(rootId);
String override = regEntry.getAttribute(REG_OVERRIDE, "true");
if (!"true".equalsIgnoreCase(override))
{ // Do not set value if override is not true and the value exist.
if (rh.getValue(keypath, name, null) != null)
{
return;
}
}
//set flag for logging previous contents if "saveprevious"
// attribute not specified or specified as 'true':
rh.setLogPrevSetValueFlag("true".equalsIgnoreCase(
regEntry.getAttribute(SAVE_PREVIOUS,"true")));
String value = regEntry.getAttribute(REG_DWORD);
if (value != null)
{ // Value type is DWord; placeholder possible.
value = substitutor.substitute(value, null);
rh.setValue(keypath, name, Long.parseLong(value));
return;
}
value = regEntry.getAttribute(REG_STRING);
if (value != null)
{ // Value type is string; placeholder possible.
value = substitutor.substitute(value, null);
rh.setValue(keypath, name, value);
return;
}
Vector<IXMLElement> values = regEntry.getChildrenNamed(REG_MULTI);
if (values != null && !values.isEmpty())
{ // Value type is REG_MULTI_SZ; placeholder possible.
Iterator<IXMLElement> multiIter = values.iterator();
String[] multiString = new String[values.size()];
for (int i = 0; multiIter.hasNext(); ++i)
{
IXMLElement element = multiIter.next();
multiString[i] = specHelper.getRequiredAttribute(element, REG_DATA);
multiString[i] = substitutor.substitute(multiString[i], null);
}
rh.setValue(keypath, name, multiString);
return;
}
values = regEntry.getChildrenNamed(REG_BIN);
if (values != null && !values.isEmpty())
{ // Value type is REG_BINARY; placeholder possible or not ??? why not
// ...
Iterator<IXMLElement> multiIter = values.iterator();
StringBuffer buf = new StringBuffer();
for (int i = 0; multiIter.hasNext(); ++i)
{
IXMLElement element = multiIter.next();
String tmp = specHelper.getRequiredAttribute(element, REG_DATA);
buf.append(tmp);
if (!tmp.endsWith(",") && multiIter.hasNext())
{
buf.append(",");
}
}
byte[] bytes = extractBytes(regEntry, substitutor.substitute(buf.toString(), null));
rh.setValue(keypath, name, bytes);
return;
}
specHelper.parseError(regEntry, "No data found.");
}
private byte[] extractBytes(IXMLElement element, String byteString) throws Exception
{
StringTokenizer st = new StringTokenizer(byteString, ",");
byte[] retval = new byte[st.countTokens()];
int i = 0;
while (st.hasMoreTokens())
{
byte value = 0;
String token = st.nextToken().trim();
try
{ // Unfortenly byte is signed ...
int tval = Integer.parseInt(token, 16);
if (tval < 0 || tval > 0xff)
{
throw new NumberFormatException("Value out of range.");
}
if (tval > 0x7f)
{
tval -= 0x100;
}
value = (byte) tval;
}
catch (NumberFormatException nfe)
{
getSpecHelper()
.parseError(element,
"Bad entry for REG_BINARY; a byte should be written as 2 digit hexvalue followed by a ','.");
}
retval[i++] = value;
}
return (retval);
}
/**
* Perform the setting of one key.
*
* @param regEntry element which contains the description of the key to be created
* @param substitutor variable substitutor to be used for revising the regEntry contents
*/
private void performKeySetting(IXMLElement regEntry, VariableSubstitutor substitutor)
throws Exception
{
String keypath = getSpecHelper().getRequiredAttribute(regEntry, REG_KEYPATH);
keypath = substitutor.substitute(keypath, null);
String root = getSpecHelper().getRequiredAttribute(regEntry, REG_ROOT);
int rootId = resolveRoot(regEntry, root, substitutor);
RegistryHandler rh = RegistryDefaultHandler.getInstance();
if (rh == null)
{
return;
}
rh.setRoot(rootId);
if (!rh.keyExist(keypath))
{
rh.createKey(keypath);
}
}
private int resolveRoot(IXMLElement regEntry, String root, VariableSubstitutor substitutor)
throws Exception
{
String root1 = substitutor.substitute(root, null);
Integer tmp = RegistryHandler.ROOT_KEY_MAP.get(root1);
if (tmp != null)
{
return (tmp);
}
getSpecHelper().parseError(regEntry, "Unknown value (" + root1 + ")for registry root.");
return 0;
}
private void initializeRegistryHandler(AutomatedInstallData idata) throws Exception
{
RegistryHandler rh = RegistryDefaultHandler.getInstance();
if (rh == null)
{
return;
}
rh.verify(idata);
getSpecHelper().readSpec(SPEC_FILE_NAME);
}
}