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