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}