001/* 002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 003 * 004 * http://www.izforge.com/izpack/ 005 * http://developer.berlios.de/projects/izpack/ 006 * 007 * Copyright 2004 Klaus Bartz 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package com.izforge.izpack.event; 023 024import java.util.Iterator; 025import java.util.List; 026import java.util.StringTokenizer; 027import java.util.Vector; 028 029import net.n3.nanoxml.XMLElement; 030 031import com.coi.tools.os.win.NativeLibException; 032import com.izforge.izpack.Pack; 033import com.izforge.izpack.installer.AutomatedInstallData; 034import com.izforge.izpack.installer.UninstallData; 035import com.izforge.izpack.installer.Unpacker; 036import com.izforge.izpack.util.AbstractUIProgressHandler; 037import com.izforge.izpack.util.SpecHelper; 038import com.izforge.izpack.util.VariableSubstitutor; 039import com.izforge.izpack.util.os.RegistryDefaultHandler; 040import com.izforge.izpack.util.os.RegistryHandler; 041import com.izforge.izpack.util.os.WrappedNativeLibException; 042 043/** 044 * Installer custom action for handling registry entries on Windows. On Unix nothing will be done. 045 * The actions which should be performed are defined in a resource file named "RegistrySpec.xml". 046 * This resource should be declared in the installation definition file (install.xml), else an 047 * exception will be raised during execution of this custom action. The related DTD is 048 * appl/install/IzPack/resources/registry.dtd. 049 * 050 * @author Klaus Bartz 051 * 052 */ 053public class RegistryInstallerListener extends NativeInstallerListener 054{ 055 056 /** The name of the XML file that specifies the registry entries. */ 057 private static final String SPEC_FILE_NAME = "RegistrySpec.xml"; 058 059 private static final String REG_KEY = "key"; 060 061 private static final String REG_VALUE = "value"; 062 063 private static final String REG_ROOT = "root"; 064 065 private static final String REG_BASENAME = "name"; 066 067 private static final String REG_KEYPATH = "keypath"; 068 069 private static final String REG_DWORD = "dword"; 070 071 private static final String REG_STRING = "string"; 072 073 private static final String REG_MULTI = "multi"; 074 075 private static final String REG_BIN = "bin"; 076 077 private static final String REG_DATA = "data"; 078 079 private static final String REG_OVERRIDE = "override"; 080 081 /** 082 * Default constructor. 083 */ 084 public RegistryInstallerListener() 085 { 086 super(true); 087 } 088 089 /* 090 * (non-Javadoc) 091 * 092 * @see com.izforge.izpack.compiler.InstallerListener#beforePacks(com.izforge.izpack.installer.AutomatedInstallData, 093 * int, com.izforge.izpack.util.AbstractUIProgressHandler) 094 */ 095 public void beforePacks(AutomatedInstallData idata, Integer npacks, 096 AbstractUIProgressHandler handler) throws Exception 097 { 098 super.beforePacks(idata, npacks, handler); 099 initializeRegistryHandler(idata); 100 } 101 102 /* 103 * (non-Javadoc) 104 * 105 * @see com.izforge.izpack.compiler.InstallerListener#afterPacks(com.izforge.izpack.installer.AutomatedInstallData, 106 * com.izforge.izpack.util.AbstractUIProgressHandler) 107 */ 108 public void afterPacks(AutomatedInstallData idata, AbstractUIProgressHandler handler) 109 throws Exception 110 { 111 try 112 { 113 // Start logging 114 RegistryHandler rh = RegistryDefaultHandler.getInstance(); 115 if (rh == null) return; 116 XMLElement uninstallerPack = null; 117 // No interrupt desired after writing registry entries. 118 Unpacker.setDiscardInterrupt(true); 119 rh.activateLogging(); 120 if (getSpecHelper().getSpec() != null) 121 { 122 VariableSubstitutor substitutor = new VariableSubstitutor(idata.getVariables()); 123 Iterator iter = idata.selectedPacks.iterator(); 124 // Get the special pack "UninstallStuff" which contains values 125 // for the uninstaller entry. 126 uninstallerPack = getSpecHelper().getPackForName("UninstallStuff"); 127 performPack(uninstallerPack, substitutor); 128 129 // Now perform the selected packs. 130 while (iter != null && iter.hasNext()) 131 { 132 // Resolve data for current pack. 133 XMLElement pack = getSpecHelper().getPackForName(((Pack) iter.next()).name); 134 performPack(pack, substitutor); 135 136 } 137 } 138 String uninstallSuffix = idata.getVariable("UninstallKeySuffix"); 139 if (uninstallSuffix != null) 140 { 141 rh.setUninstallName(rh.getUninstallName() + " " + uninstallSuffix); 142 } 143 // Generate uninstaller key automatically if not defined in spec. 144 if (uninstallerPack == null) rh.registerUninstallKey(); 145 // Get the logging info from the registry class and put it into 146 // the uninstaller. The RegistryUninstallerListener loads that data 147 // and rewind the made entries. 148 // This is the common way to transport informations from an 149 // installer CustomAction to the corresponding uninstaller 150 // CustomAction. 151 List info = rh.getLoggingInfo(); 152 if (info != null) 153 UninstallData.getInstance().addAdditionalData("registryEntries", info); 154 155 } 156 catch (Exception e) 157 { 158 if (e instanceof NativeLibException) 159 throw new WrappedNativeLibException(e); 160 else 161 throw e; 162 } 163 } 164 165 /** 166 * Performs the registry settings for the given pack. 167 * 168 * @param pack XML elemtent which contains the registry settings for one pack 169 * @throws Exception 170 */ 171 private void performPack(XMLElement pack, VariableSubstitutor substitutor) throws Exception 172 { 173 if (pack == null) return; 174 // Get all entries for registry settings. 175 Vector regEntries = pack.getChildren(); 176 if (regEntries == null) return; 177 Iterator entriesIter = regEntries.iterator(); 178 while (entriesIter != null && entriesIter.hasNext()) 179 { 180 XMLElement regEntry = (XMLElement) entriesIter.next(); 181 // Perform one registry entry. 182 String type = regEntry.getName(); 183 if (type.equalsIgnoreCase(REG_KEY)) 184 { 185 performKeySetting(regEntry, substitutor); 186 } 187 else if (type.equalsIgnoreCase(REG_VALUE)) 188 { 189 performValueSetting(regEntry, substitutor); 190 } 191 else 192 // No valid type. 193 getSpecHelper().parseError(regEntry, 194 "Non-valid type of entry; only 'key' and 'value' are allowed."); 195 196 } 197 198 } 199 200 /** 201 * Perform the setting of one value. 202 * 203 * @param regEntry element which contains the description of the value to be set 204 * @param substitutor variable substitutor to be used for revising the regEntry contents 205 */ 206 private void performValueSetting(XMLElement regEntry, VariableSubstitutor substitutor) 207 throws Exception 208 { 209 SpecHelper specHelper = getSpecHelper(); 210 String name = specHelper.getRequiredAttribute(regEntry, REG_BASENAME); 211 name = substitutor.substitute(name, null); 212 String keypath = specHelper.getRequiredAttribute(regEntry, REG_KEYPATH); 213 keypath = substitutor.substitute(keypath, null); 214 String root = specHelper.getRequiredAttribute(regEntry, REG_ROOT); 215 int rootId = resolveRoot(regEntry, root, substitutor); 216 217 RegistryHandler rh = RegistryDefaultHandler.getInstance(); 218 if (rh == null) return; 219 220 rh.setRoot(rootId); 221 222 String override = regEntry.getAttribute(REG_OVERRIDE, "true"); 223 if (!override.equalsIgnoreCase("true")) 224 { // Do not set value if override is not true and the value exist. 225 226 if (rh.getValue(keypath, name, null) != null) return; 227 } 228 229 String value = regEntry.getAttribute(REG_DWORD); 230 if (value != null) 231 { // Value type is DWord; placeholder possible. 232 value = substitutor.substitute(value, null); 233 rh.setValue(keypath, name, Long.parseLong(value)); 234 return; 235 } 236 value = regEntry.getAttribute(REG_STRING); 237 if (value != null) 238 { // Value type is string; placeholder possible. 239 value = substitutor.substitute(value, null); 240 rh.setValue(keypath, name, value); 241 return; 242 } 243 Vector values = regEntry.getChildrenNamed(REG_MULTI); 244 if (values != null && !values.isEmpty()) 245 { // Value type is REG_MULTI_SZ; placeholder possible. 246 Iterator multiIter = values.iterator(); 247 String[] multiString = new String[values.size()]; 248 for (int i = 0; multiIter.hasNext(); ++i) 249 { 250 XMLElement element = (XMLElement) multiIter.next(); 251 multiString[i] = specHelper.getRequiredAttribute(element, REG_DATA); 252 multiString[i] = substitutor.substitute(multiString[i], null); 253 } 254 rh.setValue(keypath, name, multiString); 255 return; 256 } 257 values = regEntry.getChildrenNamed(REG_BIN); 258 if (values != null && !values.isEmpty()) 259 { // Value type is REG_BINARY; placeholder possible or not ??? why not 260 // ... 261 Iterator multiIter = values.iterator(); 262 263 StringBuffer buf = new StringBuffer(); 264 for (int i = 0; multiIter.hasNext(); ++i) 265 { 266 XMLElement element = (XMLElement) multiIter.next(); 267 String tmp = specHelper.getRequiredAttribute(element, REG_DATA); 268 buf.append(tmp); 269 if (!tmp.endsWith(",") && multiIter.hasNext()) buf.append(","); 270 } 271 byte[] bytes = extractBytes(regEntry, substitutor.substitute(buf.toString(), null)); 272 rh.setValue(keypath, name, bytes); 273 return; 274 } 275 specHelper.parseError(regEntry, "No data found."); 276 277 } 278 279 private byte[] extractBytes(XMLElement element, String byteString) throws Exception 280 { 281 StringTokenizer st = new StringTokenizer(byteString, ","); 282 byte[] retval = new byte[st.countTokens()]; 283 int i = 0; 284 while (st.hasMoreTokens()) 285 { 286 byte value = 0; 287 String token = st.nextToken().trim(); 288 try 289 { // Unfortenly byte is signed ... 290 int tval = Integer.parseInt(token, 16); 291 if (tval < 0 || tval > 0xff) 292 throw new NumberFormatException("Value out of range."); 293 if (tval > 0x7f) tval -= 0x100; 294 value = (byte) tval; 295 } 296 catch (NumberFormatException nfe) 297 { 298 getSpecHelper() 299 .parseError(element, 300 "Bad entry for REG_BINARY; a byte should be written as 2 digit hexvalue followed by a ','."); 301 } 302 retval[i++] = value; 303 } 304 return (retval); 305 306 } 307 308 /** 309 * Perform the setting of one key. 310 * 311 * @param regEntry element which contains the description of the key to be created 312 * @param substitutor variable substitutor to be used for revising the regEntry contents 313 */ 314 private void performKeySetting(XMLElement regEntry, VariableSubstitutor substitutor) 315 throws Exception 316 { 317 String keypath = getSpecHelper().getRequiredAttribute(regEntry, REG_KEYPATH); 318 keypath = substitutor.substitute(keypath, null); 319 String root = getSpecHelper().getRequiredAttribute(regEntry, REG_ROOT); 320 int rootId = resolveRoot(regEntry, root, substitutor); 321 RegistryHandler rh = RegistryDefaultHandler.getInstance(); 322 if (rh == null) return; 323 rh.setRoot(rootId); 324 if (!rh.keyExist(keypath)) rh.createKey(keypath); 325 } 326 327 private int resolveRoot(XMLElement regEntry, String root, VariableSubstitutor substitutor) 328 throws Exception 329 { 330 root = substitutor.substitute(root, null); 331 Integer tmp = (Integer) RegistryHandler.ROOT_KEY_MAP.get(root); 332 if (tmp != null) return (tmp.intValue()); 333 getSpecHelper().parseError(regEntry, "Unknown value (" + root + ")for registry root."); 334 return 0; 335 } 336 337 private void initializeRegistryHandler(AutomatedInstallData idata) throws Exception 338 { 339 RegistryHandler rh = RegistryDefaultHandler.getInstance(); 340 if (rh == null) return; 341 rh.verify(idata); 342 getSpecHelper().readSpec(SPEC_FILE_NAME); 343 } 344 345}