001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu, jcarsique
016 */
017package org.nuxeo.connect.update.task.update;
018
019import java.io.File;
020import java.util.Map;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.nuxeo.connect.update.PackageException;
025import org.nuxeo.connect.update.ValidationStatus;
026import org.nuxeo.connect.update.task.Command;
027import org.nuxeo.connect.update.task.Task;
028import org.nuxeo.connect.update.task.standalone.AbstractTask;
029import org.nuxeo.connect.update.task.standalone.commands.AbstractCommand;
030import org.nuxeo.connect.update.task.standalone.commands.CompositeCommand;
031import org.nuxeo.connect.update.task.standalone.commands.DeployPlaceholder;
032import org.nuxeo.connect.update.task.update.JarUtils.Match;
033import org.nuxeo.connect.update.xml.XmlWriter;
034import org.w3c.dom.Element;
035
036/**
037 * @since 5.5
038 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
039 */
040public class Update extends AbstractCommand {
041
042    protected static final Log log = LogFactory.getLog(Update.class);
043
044    public static final String ID = "update";
045
046    /**
047     * The source file. It can be a file or a directory.
048     */
049    protected File file;
050
051    /**
052     * The target file. It can be a directory since 5.5
053     */
054    protected File todir;
055
056    protected boolean removeOnExit;
057
058    protected boolean allowDowngrade = false;
059
060    protected boolean upgradeOnly = false;
061
062    protected Update(String id) {
063        super(id);
064    }
065
066    public Update() {
067        this(ID);
068    }
069
070    @Override
071    public void initialize(Element element) throws PackageException {
072        super.initialize(element);
073    }
074
075    @Override
076    public void readFrom(Element element) throws PackageException {
077        File dir = null;
078        String v = element.getAttribute("dir");
079        if (v.length() > 0) {
080            dir = new File(v);
081        }
082        v = element.getAttribute("file");
083        if (v.length() > 0) {
084            if (dir != null) {
085                file = new File(dir, v);
086            } else {
087                file = new File(v);
088            }
089            guardVars.put("file", file);
090        } else {
091            file = dir;
092            guardVars.put("dir", dir);
093        }
094
095        v = element.getAttribute("todir");
096        if (v.length() > 0) {
097            todir = new File(v);
098            guardVars.put("todir", todir);
099        }
100
101        v = element.getAttribute("removeOnExit");
102        if (v.length() > 0) {
103            removeOnExit = Boolean.parseBoolean(v);
104        }
105        v = element.getAttribute("allowDowngrade");
106        if (v.length() > 0) {
107            allowDowngrade = Boolean.parseBoolean(v);
108        }
109        v = element.getAttribute("upgradeOnly");
110        if (v.length() > 0) {
111            upgradeOnly = Boolean.parseBoolean(v);
112        }
113    }
114
115    @Override
116    public void writeTo(XmlWriter writer) {
117        writer.start(ID);
118        if (file != null) {
119            writer.attr("file", file.getAbsolutePath());
120        }
121        if (todir != null) {
122            writer.attr("todir", todir.getAbsolutePath());
123        }
124        if (removeOnExit) {
125            writer.attr("removeOnExit", "true");
126        }
127        if (allowDowngrade) {
128            writer.attr("allowDowngrade", "true");
129        }
130        if (upgradeOnly) {
131            writer.attr("upgradeOnly", "true");
132        }
133        writer.end();
134    }
135
136    @Override
137    protected void doValidate(Task task, ValidationStatus status) throws PackageException {
138        if (file == null || todir == null) {
139            status.addError("Cannot execute command in installer."
140                    + " Invalid update syntax: file or todir was not specified.");
141        }
142        if (todir.isFile()) {
143            status.addError("Cannot execute command in installer."
144                    + " Invalid update command: todir should be a directory!");
145        }
146        if (file.isFile()) {
147            Match<String> match = JarUtils.findJarVersion(file.getName());
148            if (match == null) {
149                status.addError("Cannot execute command in installer."
150                        + " Cannot use 'update' command for non versioned files!. File name must contain a version: "
151                        + file.getName());
152            }
153        } else if (!file.isDirectory()) {
154            status.addWarning("Ignored command in installer." + " Source file not found! " + file.getName());
155        }
156    }
157
158    @Override
159    protected Command doRun(Task task, Map<String, String> prefs) throws PackageException {
160        if (!file.exists()) {
161            log.warn("Can't update using " + file + ". File is missing.");
162            return null;
163        }
164        UpdateManager mgr = ((AbstractTask) task).getUpdateManager();
165
166        Command rollback;
167        if (file.isDirectory()) {
168            rollback = updateDirectory(task, file, mgr);
169        } else {
170            rollback = updateFile(task, file, mgr);
171        }
172
173        Command deploy = getDeployCommand(mgr, rollback);
174        if (deploy != null) {
175            deploy.run(task, prefs);
176        }
177
178        return rollback;
179    }
180
181    protected CompositeCommand updateDirectory(Task task, File dir, UpdateManager mgr) throws PackageException {
182        CompositeCommand cmd = new CompositeCommand();
183        File[] files = dir.listFiles();
184        if (files != null) {
185            for (File fileInDir : files) {
186                cmd.addCommand(updateFile(task, fileInDir, mgr));
187            }
188        }
189        return cmd;
190    }
191
192    protected Rollback updateFile(Task task, File fileToUpdate, UpdateManager mgr) throws PackageException {
193        UpdateOptions opt = UpdateOptions.newInstance(task.getPackage().getId(), fileToUpdate, todir);
194        if (opt == null) {
195            return null;
196        }
197        opt.setAllowDowngrade(allowDowngrade);
198        opt.setUpgradeOnly(upgradeOnly);
199        opt.deleteOnExit = removeOnExit;
200        try {
201            RollbackOptions r = mgr.update(opt);
202            return new Rollback(r);
203        } catch (VersionAlreadyExistException e) {
204            // should never happen
205            log.error(e, e);
206            return null;
207        }
208    }
209
210    /**
211     * Method to be overridden by subclasses to provide a deploy command for hot reload
212     *
213     * @since 5.6
214     */
215    protected Command getDeployCommand(UpdateManager updateManager, Command rollbackCommand) {
216        return new DeployPlaceholder(file);
217    }
218
219}