/* * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved. * * http://izpack.org/ * http://izpack.codehaus.org/ * * Copyright 2004 Tino Schwarze * * 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.installer; import com.izforge.izpack.Pack; import com.izforge.izpack.adaptator.IXMLElement; import com.izforge.izpack.adaptator.IXMLParser; import com.izforge.izpack.adaptator.impl.XMLParser; import com.izforge.izpack.rules.Condition; import com.izforge.izpack.rules.RulesEngine; import com.izforge.izpack.util.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.*; /** * This class does alle the work for the process panel. * <p/> * It responsible for * <ul> * <li>parsing the process spec XML file * <li>performing the actions described therein * </ul> * * @author Tino Schwarze */ public class ProcessPanelWorker implements Runnable { /** * Name of resource for specifying processing parameters. */ private static final String SPEC_RESOURCE_NAME = "ProcessPanel.Spec.xml"; private VariableSubstitutor vs; protected AbstractUIProcessHandler handler; private ArrayList<ProcessingJob> jobs = new ArrayList<ProcessingJob>(); private boolean result = true; private static PrintWriter logfile = null; private String logfiledir = null; protected AutomatedInstallData idata; private Map<Boolean,List<ButtonConfig>> buttonConfigs = new Hashtable<Boolean, List<ButtonConfig>>(); /** * The constructor. * * @param idata The installation data. * @param handler The handler to notify of progress. */ public ProcessPanelWorker(AutomatedInstallData idata, AbstractUIProcessHandler handler) throws IOException { this.handler = handler; this.idata = idata; this.vs = new VariableSubstitutor(idata.getVariables()); // Removed this test in order to move out of the CTOR (ExecuteForPack // Patch) // if (!readSpec()) // throw new IOException("Error reading processing specification"); } private boolean readSpec() throws IOException { InputStream input; try { input = ResourceManager.getInstance().getInputStream(SPEC_RESOURCE_NAME); } catch (Exception e) { e.printStackTrace(); return false; } IXMLParser parser = new XMLParser(); IXMLElement spec; try { spec = parser.parse(input); } catch (Exception e) { System.err.println("Error parsing XML specification for processing."); System.err.println(e.toString()); return false; } if (!spec.hasChildren()) { return false; } // Handle logfile IXMLElement lfd = spec.getFirstChildNamed("logfiledir"); if (lfd != null) { logfiledir = lfd.getContent(); } for (IXMLElement job_el : spec.getChildrenNamed("job")) { // normally use condition attribute, but also read conditionid to not break older versions. String conditionid = job_el.hasAttribute("condition") ? job_el.getAttribute("condition") : job_el.hasAttribute("conditionid") ? job_el.getAttribute("conditionid") : null; if ((conditionid != null) && (conditionid.length() > 0)) { Debug.trace("Condition for job."); Condition cond = RulesEngine.getCondition(conditionid); if ((cond != null) && !cond.isTrue()) { Debug.trace("condition is not fulfilled."); // skip, if there is a condition and this condition isn't true continue; } } Debug.trace("Condition is fulfilled or not existent."); // ExecuteForPack Patch // Check if processing required for pack Vector<IXMLElement> forPacks = job_el.getChildrenNamed("executeForPack"); if (!jobRequiredFor(forPacks)) { continue; } // first check OS constraints - skip jobs not suited for this OS List<OsConstraint> constraints = OsConstraint.getOsList(job_el); if (OsConstraint.oneMatchesCurrentSystem(constraints)) { List<Processable> ef_list = new ArrayList<Processable>(); String job_name = job_el.getAttribute("name", ""); for (IXMLElement ef : job_el.getChildrenNamed("executefile")) { String ef_name = ef.getAttribute("name"); if ((ef_name == null) || (ef_name.length() == 0)) { System.err.println("missing \"name\" attribute for <executefile>"); return false; } List<String> args = new ArrayList<String>(); for (IXMLElement arg_el : ef.getChildrenNamed("arg")) { String arg_val = arg_el.getContent(); args.add(arg_val); } List<String> envvars = new ArrayList<String>(); for (IXMLElement env_el : ef.getChildrenNamed("env")) { String env_val = env_el.getContent(); envvars.add(env_val); } ef_list.add(new ExecutableFile(ef_name, args, envvars)); } for (IXMLElement ef : job_el.getChildrenNamed("executeclass")) { String ef_name = ef.getAttribute("name"); if ((ef_name == null) || (ef_name.length() == 0)) { System.err.println("missing \"name\" attribute for <executeclass>"); return false; } List<String> args = new ArrayList<String>(); for (IXMLElement arg_el : ef.getChildrenNamed("arg")) { String arg_val = arg_el.getContent(); args.add(arg_val); } ef_list.add(new ExecutableClass(ef_name, args)); } this.jobs.add(new ProcessingJob(job_name, ef_list)); } } buttonConfigs.put(Boolean.FALSE, new ArrayList<ButtonConfig>()); buttonConfigs.put(Boolean.TRUE, new ArrayList<ButtonConfig>()); for (IXMLElement ef : spec.getChildrenNamed("onFail")) { String conditionid = ef.hasAttribute("condition") ? ef.getAttribute("condition") : ef.hasAttribute("conditionid") ? ef.getAttribute("conditionid") : null; boolean unlockPrev = ef.hasAttribute("previous") ? Boolean.parseBoolean(ef.getAttribute("previous")) : false; boolean unlockNext = ef.hasAttribute("next") ? Boolean.parseBoolean(ef.getAttribute("next")) : false; buttonConfigs.get(Boolean.FALSE).add(new ButtonConfig(conditionid, unlockPrev, unlockNext)); } for (IXMLElement ef : spec.getChildrenNamed("onSuccess")) { String conditionid = ef.hasAttribute("condition") ? ef.getAttribute("condition") : ef.hasAttribute("conditionid") ? ef.getAttribute("conditionid") : null; boolean unlockPrev = ef.hasAttribute("previous") ? Boolean.parseBoolean(ef.getAttribute("previous")) : false; buttonConfigs.get(Boolean.TRUE).add(new ButtonConfig(conditionid, unlockPrev, true)); } return true; } /** * This is called when the processing thread is activated. * <p/> * Can also be called directly if asynchronous processing is not desired. */ public void run() { // ExecuteForPack patch // Read spec only here... not before, cause packs are otherwise // all selected or de-selected try { if (!readSpec()) { System.err.println("Error parsing XML specification for processing."); return; } } catch (java.io.IOException ioe) { System.err.println(ioe.toString()); return; } // Create logfile if needed. Do it at this point because // variable substitution needs selected install path. if (logfiledir != null) { logfiledir = IoHelper.translatePath(logfiledir, new VariableSubstitutor(idata .getVariables())); File lf; String appVersion = idata.getVariable("APP_VER"); if (appVersion != null) { appVersion = "V" + appVersion; } else { appVersion = "undef"; } String identifier = (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()); identifier = appVersion.replace(' ', '_') + "_" + identifier; try { lf = File.createTempFile("Install_" + identifier + "_", ".log", new File(logfiledir)); logfile = new PrintWriter(new FileOutputStream(lf), true); } catch (IOException e) { Debug.error(e); // TODO throw or throw not, that's the question... } } this.handler.startProcessing(this.jobs.size()); for (ProcessingJob pj : this.jobs) { this.handler.startProcess(pj.name); this.result = pj.run(this.handler, this.vs); this.handler.finishProcess(); if (!this.result) { break; } } boolean unlockNext = true; boolean unlockPrev = false; // get the ButtonConfigs matching the this.result for (ButtonConfig bc : buttonConfigs.get(Boolean.valueOf(this.result))) { String conditionid = bc.getConditionid(); if ((conditionid != null) && (conditionid.length() > 0)) { Debug.trace("Condition for job."); Condition cond = RulesEngine.getCondition(conditionid); if ((cond != null) && !cond.isTrue()) { Debug.trace("condition is not fulfilled."); // skip, if there is a condition and this condition isn't true continue; } } unlockNext = bc.isUnlockNext(); unlockPrev = bc.isUnlockPrev(); break; } this.handler.finishProcessing(unlockPrev, unlockNext); if (logfile != null) { logfile.close(); } } /** * Start the compilation in a separate thread. */ public void startThread() { Thread processingThread = new Thread(this, "processing thread"); // will call this.run() processingThread.start(); } /** * Return the result of the process execution. * * @return true if all processes succeeded, false otherwise. */ public boolean getResult() { return this.result; } interface Processable { /** * @param handler The UI handler for user interaction and to send output to. * @return true on success, false if processing should stop */ public boolean run(AbstractUIProcessHandler handler, VariableSubstitutor vs); } private static class ProcessingJob implements Processable { public String name; private List<Processable> processables; public ProcessingJob(String name, List<Processable> processables) { this.name = name; this.processables = processables; } public boolean run(AbstractUIProcessHandler handler, VariableSubstitutor vs) { for (Processable pr : this.processables) { if (!pr.run(handler, vs)) { return false; } } return true; } } private static class ExecutableFile implements Processable { private String filename; private List<String> arguments; private List<String> envvariables; protected AbstractUIProcessHandler handler; public ExecutableFile(String fn, List<String> args, List<String> envvars) { this.filename = fn; this.arguments = args; this.envvariables = envvars; } public boolean run(AbstractUIProcessHandler handler, VariableSubstitutor vs) { this.handler = handler; List<String> params = new ArrayList<String>(this.arguments.size() + 1); params.add(vs.substitute(this.filename, "plain")); for (String argument : this.arguments) { params.add(vs.substitute(argument, "plain")); } ProcessBuilder pb = new ProcessBuilder(params); Map<String, String> environment = pb.environment(); for (String envvar : envvariables) { String ev = vs.substitute(envvar, "plain"); int i = ev.indexOf("="); if (i > 0) { environment.put(ev.substring(0, i), ev.substring(i + 1)); } } try { Process p = pb.start(); OutputMonitor stdoutMon = new OutputMonitor(this.handler, p.getInputStream(), false); OutputMonitor stderrMon = new OutputMonitor(this.handler, p.getErrorStream(), true); Thread stdoutThread = new Thread(stdoutMon); Thread stderrThread = new Thread(stderrMon); stdoutThread.setDaemon(true); stderrThread.setDaemon(true); stdoutThread.start(); stderrThread.start(); try { int exitStatus = p.waitFor(); stopMonitor(stdoutMon, stdoutThread); stopMonitor(stderrMon, stderrThread); if (exitStatus != 0) { if (this.handler.askQuestion("Process execution failed", "Continue anyway?", AbstractUIHandler.CHOICES_YES_NO, AbstractUIHandler.ANSWER_YES) == AbstractUIHandler.ANSWER_NO) { return false; } } } catch (InterruptedException ie) { p.destroy(); this.handler.emitError("process interrupted", ie.toString()); return false; } } catch (IOException ioe) { this.handler.emitError("I/O error", ioe.toString()); return false; } return true; } private void stopMonitor(OutputMonitor m, Thread t) { // taken from com.izforge.izpack.util.FileExecutor m.doStop(); long softTimeout = 500; try { t.join(softTimeout); } catch (InterruptedException e) { } if (!t.isAlive()) { return; } t.interrupt(); long hardTimeout = 500; try { t.join(hardTimeout); } catch (InterruptedException e) { } } static public class OutputMonitor implements Runnable { private boolean stderr = false; private AbstractUIProcessHandler handler; private BufferedReader reader; private Boolean stop = false; public OutputMonitor(AbstractUIProcessHandler handler, InputStream is, boolean stderr) { this.stderr = stderr; this.reader = new BufferedReader(new InputStreamReader(is)); this.handler = handler; } public void run() { try { String line; while ((line = reader.readLine()) != null) { this.handler.logOutput(line, stderr); // log output also to file given in ProcessPanelSpec if (logfile != null) { logfile.println(line); } synchronized (this.stop) { if (stop) { return; } } } } catch (IOException ioe) { this.handler.logOutput(ioe.toString(), true); // log errors also to file given in ProcessPanelSpec if (logfile != null) { logfile.println(ioe.toString()); } } } public void doStop() { synchronized (this.stop) { this.stop = true; } } } } /** * Tries to create a class that has an empty contstructor and a method * run(AbstractUIProcessHandler, String[]) If found, it calls the method and processes all * returned exceptions */ private static class ExecutableClass implements Processable { final private String myClassName; final private List<String> myArguments; protected AbstractUIProcessHandler myHandler; public ExecutableClass(String className, List<String> args) { myClassName = className; myArguments = args; } public boolean run(AbstractUIProcessHandler aHandler, VariableSubstitutor varSubstitutor) { boolean result = false; myHandler = aHandler; String params[] = new String[myArguments.size()]; int i = 0; for (String myArgument : myArguments) { params[i++] = varSubstitutor.substitute(myArgument, "plain"); } try { ClassLoader loader = this.getClass().getClassLoader(); Class procClass = loader.loadClass(myClassName); Object o = procClass.newInstance(); Method m = procClass.getMethod("run", new Class[]{AbstractUIProcessHandler.class, String[].class}); if (m.getReturnType().getName().equals("boolean")) { result = ((Boolean) m.invoke(o, new Object[] { myHandler, params})) .booleanValue(); } else { m.invoke(o, new Object[] { myHandler, params}); result = true; } } catch (SecurityException e) { myHandler.emitError("Post Processing Error", "Security exception thrown when processing class: " + myClassName); } catch (ClassNotFoundException e) { myHandler.emitError("Post Processing Error", "Cannot find processing class: " + myClassName); } catch (NoSuchMethodException e) { myHandler.emitError("Post Processing Error", "Processing class does not have 'run' method: " + myClassName); } catch (IllegalAccessException e) { myHandler.emitError("Post Processing Error", "Error accessing processing class: " + myClassName); } catch (InvocationTargetException e) { myHandler.emitError("Post Processing Error", "Invocation Problem calling : " + myClassName + ", " + e.getCause().getMessage()); } catch (Exception e) { myHandler.emitError("Post Processing Error", "Exception when running processing class: " + myClassName + ", " + e.getMessage()); } catch (Error e) { myHandler.emitError("Post Processing Error", "Error when running processing class: " + myClassName + ", " + e.getMessage()); } catch (Throwable e) { myHandler.emitError("Post Processing Error", "Error when running processing class: " + myClassName + ", " + e.getMessage()); } return result; } } /*------------------------ ExecuteForPack PATCH -------------------------*/ /* * Verifies if the job is required for any of the packs listed. The job is required for a pack * in the list if that pack is actually selected for installation. <br><br> <b>Note:</b><br> * If the list of selected packs is empty then <code>true</code> is always returned. The same * is true if the <code>packs</code> list is empty. * * @param packs a <code>Vector</code> of <code>String</code>s. Each of the strings denotes * a pack for which the schortcut should be created if the pack is actually installed. * * @return <code>true</code> if the shortcut is required for at least on pack in the list, * otherwise returns <code>false</code>. */ /*--------------------------------------------------------------------------*/ /* * @design * * The information about the installed packs comes from InstallData.selectedPacks. This assumes * that this panel is presented to the user AFTER the PacksPanel. * * /*-------------------------------------------------------------------------- */ private boolean jobRequiredFor(Vector<IXMLElement> packs) { String selected; String required; if (packs.size() == 0) { return (true); } // System.out.println ("Number of selected packs is " // +idata.selectedPacks.size () ); for (int i = 0; i < idata.selectedPacks.size(); i++) { selected = ((Pack) idata.selectedPacks.get(i)).name; // System.out.println ("Selected pack is " + selected); for (int k = 0; k < packs.size(); k++) { required = (packs.elementAt(k)).getAttribute("name", ""); // System.out.println ("Attribute name is " + required); if (selected.equals(required)) { // System.out.println ("Return true"); return (true); } } } return (false); } } class ButtonConfig { private final String conditionid; private final boolean unlockPrev; private final boolean unlockNext; /** * @param conditionid * @param unlockPrev * @param unlockNext */ public ButtonConfig(String conditionid, boolean unlockPrev, boolean unlockNext) { this.conditionid = conditionid; this.unlockPrev = unlockPrev; this.unlockNext = unlockNext; } /** * @return the unlockPrev */ public boolean isUnlockPrev() { return unlockPrev; } /** * @return the unlockNext */ public boolean isUnlockNext() { return unlockNext; } /** * @return the conditionid */ public String getConditionid() { return conditionid; } }