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        }
144        if (todir.isFile()) {
145            status.addError("Cannot execute command in installer."
146                    + " Invalid update command: todir should be a directory!");
147        }
148        if (file.isFile()) {
149            Match<String> match = JarUtils.findJarVersion(file.getName());
150            if (match == null) {
151                status.addError("Cannot execute command in installer."
152                        + " Cannot use 'update' command for non versioned files!. File name must contain a version: "
153                        + file.getName());
154            }
155        } else if (!file.isDirectory()) {
156            status.addWarning("Ignored command in installer." + " Source file not found! " + file.getName());
157        }
158    }
159
160    @Override
161    protected Command doRun(Task task, Map<String, String> prefs) throws PackageException {
162        if (!file.exists()) {
163            log.warn("Can't update using " + file + ". File is missing.");
164            return null;
165        }
166        UpdateManager mgr = ((AbstractTask) task).getUpdateManager();
167
168        Command rollback;
169        if (file.isDirectory()) {
170            rollback = updateDirectory(task, file, mgr);
171        } else {
172            rollback = updateFile(task, file, mgr);
173        }
174
175        Command deploy = getDeployCommand(mgr, rollback);
176        if (deploy != null) {
177            deploy.run(task, prefs);
178        }
179
180        return rollback;
181    }
182
183    protected CompositeCommand updateDirectory(Task task, File dir, UpdateManager mgr) throws PackageException {
184        CompositeCommand cmd = new CompositeCommand();
185        File[] files = dir.listFiles();
186        if (files != null) {
187            for (File fileInDir : files) {
188                cmd.addCommand(updateFile(task, fileInDir, mgr));
189            }
190        }
191        return cmd;
192    }
193
194    protected Rollback updateFile(Task task, File fileToUpdate, UpdateManager mgr) throws PackageException {
195        UpdateOptions opt = UpdateOptions.newInstance(task.getPackage().getId(), fileToUpdate, todir);
196        if (opt == null) {
197            return null;
198        }
199        opt.setAllowDowngrade(allowDowngrade);
200        opt.setUpgradeOnly(upgradeOnly);
201        opt.deleteOnExit = removeOnExit;
202        try {
203            RollbackOptions r = mgr.update(opt);
204            return new Rollback(r);
205        } catch (VersionAlreadyExistException e) {
206            // should never happen
207            log.error(e, e);
208            return null;
209        }
210    }
211
212    /**
213     * Method to be overridden by subclasses to provide a deploy command for hot reload
214     *
215     * @since 5.6
216     */
217    protected Command getDeployCommand(UpdateManager updateManager, Command rollbackCommand) {
218        return new DeployPlaceholder(file);
219    }
220
221}