001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 2003 Jonathan Halliday
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:
022: package com.izforge.izpack.installer;
023:
024: import java.io.BufferedWriter;
025: import java.io.ByteArrayOutputStream;
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.InputStream;
029: import java.io.ObjectOutputStream;
030: import java.io.OutputStreamWriter;
031: import java.util.ArrayList;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.TreeMap;
037: import java.util.Vector;
038: import java.util.zip.ZipEntry;
039: import java.util.zip.ZipException;
040: import java.util.zip.ZipOutputStream;
041:
042: import net.n3.nanoxml.NonValidator;
043: import net.n3.nanoxml.StdXMLParser;
044: import net.n3.nanoxml.StdXMLReader;
045: import net.n3.nanoxml.XMLElement;
046: import net.n3.nanoxml.XMLBuilderFactory;
047:
048: import com.izforge.izpack.CustomData;
049: import com.izforge.izpack.ExecutableFile;
050: import com.izforge.izpack.LocaleDatabase;
051: import com.izforge.izpack.Panel;
052: import com.izforge.izpack.util.Debug;
053: import com.izforge.izpack.util.Housekeeper;
054: import com.izforge.izpack.util.OsConstraint;
055:
056: /**
057: * Runs the install process in text only (no GUI) mode.
058: *
059: * @author Jonathan Halliday <jonathan.halliday@arjuna.com>
060: * @author Julien Ponge <julien@izforge.com>
061: * @author Johannes Lehtinen <johannes.lehtinen@iki.fi>
062: */
063: public class AutomatedInstaller extends InstallerBase {
064:
065: // there are panels which can be instantiated multiple times
066: // we therefore need to select the right XML section for each
067: // instance
068: private TreeMap<String, Integer> panelInstanceCount;
069:
070: /** The automated installation data. */
071: private AutomatedInstallData idata = new AutomatedInstallData();
072:
073: /** The result of the installation. */
074: private boolean result = false;
075:
076: /**
077: * Constructing an instance triggers the install.
078: *
079: * @param inputFilename Name of the file containing the installation data.
080: * @exception Exception Description of the Exception
081: */
082: public AutomatedInstaller(String inputFilename) throws Exception {
083: super ();
084:
085: File input = new File(inputFilename);
086:
087: // Loads the installation data
088: loadInstallData(this .idata);
089:
090: // Loads the xml data
091: this .idata.xmlData = getXMLData(input);
092:
093: // Loads the langpack
094: this .idata.localeISO3 = this .idata.xmlData.getAttribute(
095: "langpack", "eng");
096: InputStream in = getClass().getResourceAsStream(
097: "/langpacks/" + this .idata.localeISO3 + ".xml");
098: this .idata.langpack = new LocaleDatabase(in);
099: this .idata.setVariable(ScriptParser.ISO3_LANG,
100: this .idata.localeISO3);
101:
102: // create the resource manager singleton
103: ResourceManager.create(this .idata);
104:
105: // Load custom langpack if exist.
106: addCustomLangpack(this .idata);
107:
108: this .panelInstanceCount = new TreeMap<String, Integer>();
109: }
110:
111: /**
112: * Writes the uninstalldata.
113: *
114: * Unfortunately, Java doesn't allow multiple inheritance, so <code>AutomatedInstaller</code>
115: * and <code>InstallerFrame</code> can't share this code ... :-/
116: *
117: * TODO: We should try to fix this in the future.
118: */
119: private boolean writeUninstallData() {
120: try {
121: // We get the data
122: UninstallData udata = UninstallData.getInstance();
123: List files = udata.getUninstalableFilesList();
124: ZipOutputStream outJar = this .idata.uninstallOutJar;
125:
126: if (outJar == null)
127: return true; // it is allowed not to have an installer
128:
129: System.out.println("[ Writing the uninstaller data ... ]");
130:
131: // We write the files log
132: outJar.putNextEntry(new ZipEntry("install.log"));
133: BufferedWriter logWriter = new BufferedWriter(
134: new OutputStreamWriter(outJar));
135: logWriter.write(this .idata.getInstallPath());
136: logWriter.newLine();
137: Iterator iter = files.iterator();
138: while (iter.hasNext()) {
139: logWriter.write((String) iter.next());
140: if (iter.hasNext())
141: logWriter.newLine();
142: }
143: logWriter.flush();
144: outJar.closeEntry();
145:
146: // We write the uninstaller jar file log
147: outJar.putNextEntry(new ZipEntry("jarlocation.log"));
148: logWriter = new BufferedWriter(new OutputStreamWriter(
149: outJar));
150: logWriter.write(udata.getUninstallerJarFilename());
151: logWriter.newLine();
152: logWriter.write(udata.getUninstallerPath());
153: logWriter.flush();
154: outJar.closeEntry();
155:
156: // Write out executables to execute on uninstall
157: outJar.putNextEntry(new ZipEntry("executables"));
158: ObjectOutputStream execStream = new ObjectOutputStream(
159: outJar);
160: iter = udata.getExecutablesList().iterator();
161: execStream.writeInt(udata.getExecutablesList().size());
162: while (iter.hasNext()) {
163: ExecutableFile file = (ExecutableFile) iter.next();
164: execStream.writeObject(file);
165: }
166: execStream.flush();
167: outJar.closeEntry();
168:
169: // *** ADDED code bellow
170: // Write out additional uninstall data
171: // Do not "kill" the installation if there is a problem
172: // with custom uninstall data. Therefore log it to Debug,
173: // but do not throw.
174: Map<String, Object> additionalData = udata
175: .getAdditionalData();
176: if (additionalData != null && !additionalData.isEmpty()) {
177: Iterator<String> keys = additionalData.keySet()
178: .iterator();
179: HashSet<String> exist = new HashSet<String>();
180: while (keys != null && keys.hasNext()) {
181: String key = keys.next();
182: Object contents = additionalData.get(key);
183: if ("__uninstallLibs__".equals(key)) {
184: Iterator nativeLibIter = ((List) contents)
185: .iterator();
186: while (nativeLibIter != null
187: && nativeLibIter.hasNext()) {
188: String nativeLibName = (String) ((List) nativeLibIter
189: .next()).get(0);
190: byte[] buffer = new byte[5120];
191: long bytesCopied = 0;
192: int bytesInBuffer;
193: outJar.putNextEntry(new ZipEntry("native/"
194: + nativeLibName));
195: InputStream in = getClass()
196: .getResourceAsStream(
197: "/native/" + nativeLibName);
198: while ((bytesInBuffer = in.read(buffer)) != -1) {
199: outJar.write(buffer, 0, bytesInBuffer);
200: bytesCopied += bytesInBuffer;
201: }
202: outJar.closeEntry();
203: }
204: } else if ("uninstallerListeners".equals(key)
205: || "uninstallerJars".equals(key)) { // It is a ArrayList of ArrayLists which contains the
206: // full
207: // package paths of all needed class files.
208: // First we create a new ArrayList which contains only
209: // the full paths for the uninstall listener self; thats
210: // the first entry of each sub ArrayList.
211: ArrayList<String> subContents = new ArrayList<String>();
212:
213: // Secound put the class into uninstaller.jar
214: Iterator listenerIter = ((List) contents)
215: .iterator();
216: while (listenerIter.hasNext()) {
217: byte[] buffer = new byte[5120];
218: long bytesCopied = 0;
219: int bytesInBuffer;
220: CustomData customData = (CustomData) listenerIter
221: .next();
222: // First element of the list contains the listener
223: // class path;
224: // remind it for later.
225: if (customData.listenerName != null)
226: subContents
227: .add(customData.listenerName);
228: Iterator<String> liClaIter = customData.contents
229: .iterator();
230: while (liClaIter.hasNext()) {
231: String contentPath = liClaIter.next();
232: if (exist.contains(contentPath))
233: continue;
234: exist.add(contentPath);
235: try {
236: outJar.putNextEntry(new ZipEntry(
237: contentPath));
238: } catch (ZipException ze) { // Ignore, or ignore not ?? May be it is a
239: // exception because
240: // a doubled entry was tried, then we should
241: // ignore ...
242: Debug
243: .trace("ZipException in writing custom data: "
244: + ze.getMessage());
245: continue;
246: }
247: InputStream in = getClass()
248: .getResourceAsStream(
249: "/" + contentPath);
250: if (in != null) {
251: while ((bytesInBuffer = in
252: .read(buffer)) != -1) {
253: outJar.write(buffer, 0,
254: bytesInBuffer);
255: bytesCopied += bytesInBuffer;
256: }
257: } else
258: Debug
259: .trace("custom data not found: "
260: + contentPath);
261: outJar.closeEntry();
262:
263: }
264: }
265: // Third we write the list into the
266: // uninstaller.jar
267: outJar.putNextEntry(new ZipEntry(key));
268: ObjectOutputStream objOut = new ObjectOutputStream(
269: outJar);
270: objOut.writeObject(subContents);
271: objOut.flush();
272: outJar.closeEntry();
273:
274: } else {
275: outJar.putNextEntry(new ZipEntry(key));
276: if (contents instanceof ByteArrayOutputStream) {
277: ((ByteArrayOutputStream) contents)
278: .writeTo(outJar);
279: } else {
280: ObjectOutputStream objOut = new ObjectOutputStream(
281: outJar);
282: objOut.writeObject(contents);
283: objOut.flush();
284: }
285: outJar.closeEntry();
286: }
287: }
288: }
289: // write the files which should be deleted by root for another user
290:
291: outJar.putNextEntry(new ZipEntry(UninstallData.ROOTSCRIPT));
292: ObjectOutputStream rootStream = new ObjectOutputStream(
293: outJar);
294:
295: String rootScript = udata.getRootScript();
296:
297: rootStream.writeUTF(rootScript);
298:
299: rootStream.flush();
300: outJar.closeEntry();
301:
302: // *** ADDED to this point
303:
304: // Cleanup
305: outJar.flush();
306: outJar.close();
307: return true;
308: } catch (Exception err) {
309: err.printStackTrace();
310: return false;
311: }
312: }
313:
314: /**
315: * Runs the automated installation logic for each panel in turn.
316: *
317: * @throws Exception
318: */
319: protected void doInstall() throws Exception {
320: // TODO: i18n
321: System.out.println("[ Starting automated installation ]");
322: Debug.log("[ Starting automated installation ]");
323:
324: try {
325: // assume that installation will succeed
326: this .result = true;
327:
328: // walk the panels in order
329: Iterator panelsIterator = this .idata.panelsOrder.iterator();
330: while (panelsIterator.hasNext()) {
331: Panel p = (Panel) panelsIterator.next();
332:
333: String praefix = "com.izforge.izpack.panels.";
334: if (p.className.compareTo(".") > -1)
335: // Full qualified class name
336: praefix = "";
337: if (!OsConstraint
338: .oneMatchesCurrentSystem(p.osConstraints))
339: continue;
340:
341: String panelClassName = p.className;
342: String automationHelperClassName = praefix
343: + panelClassName + "AutomationHelper";
344: Class<PanelAutomation> automationHelperClass = null;
345:
346: Debug.log("AutomationHelper:"
347: + automationHelperClassName);
348: // determine if the panel supports automated install
349: try {
350:
351: automationHelperClass = (Class<PanelAutomation>) Class
352: .forName(automationHelperClassName);
353:
354: } catch (ClassNotFoundException e) {
355: // this is OK - not all panels have/need automation support.
356: Debug.log("ClassNotFoundException-skip :"
357: + automationHelperClassName);
358: continue;
359: }
360:
361: // instantiate the automation logic for the panel
362: PanelAutomation automationHelperInstance = null;
363: if (automationHelperClass != null) {
364: try {
365: Debug.log("Instantiate :"
366: + automationHelperClassName);
367: automationHelperInstance = automationHelperClass
368: .newInstance();
369: } catch (Exception e) {
370: Debug.log("ERROR: no default constructor for "
371: + automationHelperClassName
372: + ", skipping...");
373: continue;
374: }
375: }
376:
377: // We get the panels root xml markup
378: Vector<XMLElement> panelRoots = this .idata.xmlData
379: .getChildrenNamed(panelClassName);
380: int panelRootNo = 0;
381:
382: if (this .panelInstanceCount.containsKey(panelClassName)) {
383: // get number of panel instance to process
384: panelRootNo = this .panelInstanceCount
385: .get(panelClassName);
386: }
387:
388: XMLElement panelRoot = panelRoots
389: .elementAt(panelRootNo);
390:
391: this .panelInstanceCount.put(panelClassName,
392: panelRootNo + 1);
393:
394: // execute the installation logic for the current panel, if it has
395: // any:
396: if (automationHelperInstance != null) {
397: try {
398: Debug
399: .log("automationHelperInstance.runAutomated :"
400: + automationHelperClassName
401: + " entered.");
402: if (!automationHelperInstance.runAutomated(
403: this .idata, panelRoot)) {
404: // make installation fail instantly
405: this .result = false;
406: return;
407: } else {
408: Debug
409: .log("automationHelperInstance.runAutomated :"
410: + automationHelperClassName
411: + " successfully done.");
412: }
413: } catch (Exception e) {
414: Debug
415: .log("ERROR: automated installation failed for panel "
416: + panelClassName);
417: e.printStackTrace();
418: this .result = false;
419: }
420:
421: }
422:
423: }
424:
425: // this does nothing if the uninstaller was not included
426: writeUninstallData();
427:
428: if (this .result)
429: System.out.println("[ Automated installation done ]");
430: else
431: System.out
432: .println("[ Automated installation FAILED! ]");
433: } catch (Exception e) {
434: this .result = false;
435: System.err.println(e.toString());
436: e.printStackTrace();
437: System.out.println("[ Automated installation FAILED! ]");
438: } finally {
439: // Bye
440: Housekeeper.getInstance().shutDown(this .result ? 0 : 1);
441: }
442: }
443:
444: /**
445: * Loads the xml data for the automated mode.
446: *
447: * @param input The file containing the installation data.
448: *
449: * @return The root of the XML file.
450: *
451: * @exception Exception thrown if there are problems reading the file.
452: */
453: public XMLElement getXMLData(File input) throws Exception {
454: FileInputStream in = new FileInputStream(input);
455:
456: // Initialises the parser
457: StdXMLParser parser = new StdXMLParser();
458: parser.setBuilder(XMLBuilderFactory.createXMLBuilder());
459: parser.setReader(new StdXMLReader(in));
460: parser.setValidator(new NonValidator());
461:
462: XMLElement rtn = (XMLElement) parser.parse();
463: in.close();
464:
465: return rtn;
466: }
467:
468: /**
469: * Get the result of the installation.
470: *
471: * @return True if the installation was successful.
472: */
473: public boolean getResult() {
474: return this.result;
475: }
476: }
|