001/*
002 * (C) Copyright 2006-2014 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-2.1.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.standalone;
018
019import java.io.ByteArrayOutputStream;
020import java.io.File;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.PrintStream;
024import java.text.SimpleDateFormat;
025import java.util.Date;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.Properties;
029
030import org.apache.commons.collections.MapUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033
034import org.nuxeo.common.Environment;
035import org.nuxeo.common.utils.FileUtils;
036import org.nuxeo.common.utils.StringUtils;
037import org.nuxeo.connect.update.LocalPackage;
038import org.nuxeo.connect.update.PackageException;
039import org.nuxeo.connect.update.PackageState;
040import org.nuxeo.connect.update.PackageUpdateService;
041import org.nuxeo.connect.update.ValidationStatus;
042import org.nuxeo.connect.update.task.Task;
043import org.nuxeo.connect.update.task.update.UpdateManager;
044
045/**
046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
047 */
048public abstract class AbstractTask implements Task {
049
050    static final Log log = LogFactory.getLog(AbstractTask.class);
051
052    public static final String PKG_ID = "package.id";
053
054    public static final String PKG_NAME = "package.name";
055
056    public static final String PKG_VERSION = "package.version";
057
058    public static final String PKG_ROOT = "package.root";
059
060    public static final String ENV_HOME = "env.home";
061
062    /**
063     * @since 5.5
064     */
065    public static final String ENV_SERVER_HOME = "env.server.home";
066
067    /**
068     * Set only on JBoss - the EAR root directory path
069     */
070    public static final String ENV_EAR = "env.ear";
071
072    public static final String ENV_LIB = "env.lib";
073
074    public static final String ENV_SYSLIB = "env.syslib";
075
076    public static final String ENV_BUNDLES = "env.bundles";
077
078    public static final String ENV_CONFIG = "env.config";
079
080    /**
081     * @since 5.5
082     */
083    public static final String ENV_TEMPLATES = "env.templates";
084
085    public static final String ENV_TIMESTAMP = "sys.timestamp";
086
087    /**
088     * The host application name.
089     *
090     * @see Environment#getHostApplicationName()
091     */
092    public static final String ENV_HOSTAPP_NAME = "env.hostapp.name";
093
094    /**
095     * The host application version
096     *
097     * @see Environment#getHostApplicationVersion()
098     */
099    public static final String ENV_HOSTAPP_VERSION = "env.hostapp.version";
100
101    protected boolean restart;
102
103    protected LocalPackage pkg;
104
105    protected String serverPathPrefix;
106
107    protected UpdateManager updateMgr;
108
109    protected boolean updateMgrLoaded = false;
110
111    protected PackageUpdateService service;
112
113    /**
114     * A map of environment key/values that can be used in XML install files as variables.
115     */
116    protected final Map<String, String> env;
117
118    public AbstractTask(PackageUpdateService pus) {
119        service = pus;
120        env = new HashMap<>();
121        Environment nxenv = Environment.getDefault();
122        File serverHome = nxenv.getServerHome();
123        File nxHome = nxenv.getRuntimeHome();
124        File config = nxenv.getConfig();
125        serverPathPrefix = serverHome.getAbsolutePath();
126        if (!serverPathPrefix.endsWith(File.separator)) {
127            serverPathPrefix = serverPathPrefix.concat(File.separator);
128        }
129        env.put(ENV_SERVER_HOME, serverHome.getAbsolutePath());
130        env.put(ENV_HOME, nxHome.getAbsolutePath());
131        env.put(ENV_CONFIG, config.getAbsolutePath());
132        env.put(ENV_HOSTAPP_NAME, nxenv.getHostApplicationName());
133        env.put(ENV_HOSTAPP_VERSION, nxenv.getHostApplicationVersion());
134        env.put(ENV_SYSLIB, new File(serverHome, "lib").getAbsolutePath());
135        if (nxenv.isJBoss()) {
136            File ear = config.getParentFile();
137            env.put(ENV_EAR, ear.getAbsolutePath());
138            env.put(ENV_LIB, new File(ear, "lib").getAbsolutePath());
139            env.put(ENV_BUNDLES, new File(ear, "bundles").getAbsolutePath());
140        } else {
141            env.put(ENV_LIB, new File(nxHome, "lib").getAbsolutePath());
142            env.put(ENV_BUNDLES, new File(nxHome, "bundles").getAbsolutePath());
143        }
144        env.put(ENV_TEMPLATES, new File(serverHome, "templates").getAbsolutePath());
145        env.put(ENV_TIMESTAMP, new SimpleDateFormat("yyMMddHHmmss").format(new Date()));
146        updateMgr = new UpdateManager(serverHome, service.getRegistry());
147    }
148
149    public abstract boolean isInstallTask();
150
151    @Override
152    @SuppressWarnings("hiding")
153    public void initialize(LocalPackage pkg, boolean restart) throws PackageException {
154        this.pkg = pkg;
155        this.restart = restart;
156        env.put(PKG_ID, pkg.getId());
157        env.put(PKG_NAME, pkg.getName());
158        env.put(PKG_VERSION, pkg.getVersion().toString());
159        env.put(PKG_ROOT, pkg.getData().getRoot().getAbsolutePath());
160
161        if (log.isDebugEnabled()) {
162            final ByteArrayOutputStream out = new ByteArrayOutputStream();
163            final PrintStream outPrint = new PrintStream(out);
164            MapUtils.debugPrint(outPrint, null, env);
165            log.debug(out.toString());
166        }
167    }
168
169    /**
170     * Get a file given its key in the environment map. If no key exists then null is returned.
171     *
172     * @param key
173     */
174    public File getFile(String key) {
175        String val = env.get(key);
176        return val == null ? null : new File(val);
177    }
178
179    @Override
180    public boolean isRestartRequired() {
181        return restart;
182    }
183
184    @Override
185    public LocalPackage getPackage() {
186        return pkg;
187    }
188
189    protected Map<Object, Object> createContextMap(Map<String, String> params) {
190        Map<Object, Object> map = new HashMap<>(System.getProperties());
191        map.putAll(env);
192        if (params != null && !params.isEmpty()) {
193            map.putAll(params);
194        }
195        return map;
196    }
197
198    protected String loadParametrizedFile(File file, Map<String, String> params) throws IOException {
199        String content = FileUtils.readFile(file);
200        // replace variables.
201        return StringUtils.expandVars(content, createContextMap(params));
202    }
203
204    protected void saveParams(Map<String, String> params) throws PackageException {
205        if (params == null || params.isEmpty()) {
206            return;
207        }
208        try {
209            Properties props = new Properties();
210            props.putAll(params);
211            File file = pkg.getData().getEntry(LocalPackage.INSTALL_PROPERTIES);
212            FileOutputStream out = new FileOutputStream(file);
213            try {
214                props.store(out, "user install parameters");
215            } finally {
216                out.close();
217            }
218        } catch (IOException e) {
219            throw new PackageException("Failed to save install parameters", e);
220        }
221    }
222
223    @Override
224    public synchronized void run(Map<String, String> params) throws PackageException {
225        if (isInstallTask()) {
226            LocalPackage oldpkg = service.getActivePackage(pkg.getName());
227            if (oldpkg != null) {
228                if (oldpkg.getPackageState() == PackageState.INSTALLING) {
229                    throw new PackageException("Another package with the same name is installing: " + oldpkg.getName());
230                } else {
231                    // uninstall it.
232                    Task utask = oldpkg.getUninstallTask();
233                    try {
234                        utask.run(new HashMap<String, String>());
235                    } catch (PackageException e) {
236                        utask.rollback();
237                        throw new PackageException("Failed to uninstall: " + oldpkg.getId()
238                                + ". Cannot continue installation of " + pkg.getId(), e);
239                    }
240                }
241            }
242        }
243        service.setPackageState(pkg, PackageState.INSTALLING);
244        saveParams(params);
245        doRun(params);
246        taskDone();
247        if (updateMgrLoaded) {
248            updateMgr.store();
249        }
250    }
251
252    public synchronized UpdateManager getUpdateManager() throws PackageException {
253        if (!updateMgrLoaded) {
254            updateMgr.load();
255            updateMgrLoaded = true;
256        }
257        return updateMgr;
258    }
259
260    protected abstract void rollbackDone() throws PackageException;
261
262    protected abstract void taskDone() throws PackageException;
263
264    @Override
265    public void rollback() throws PackageException {
266        try {
267            doRollback();
268        } finally {
269            rollbackDone();
270        }
271    }
272
273    @Override
274    public void setRestartRequired(boolean isRestartRequired) {
275        this.restart = isRestartRequired;
276    }
277
278    protected abstract void doRun(Map<String, String> params) throws PackageException;
279
280    protected abstract void doRollback() throws PackageException;
281
282    @Override
283    public ValidationStatus validate() throws PackageException {
284        ValidationStatus status = new ValidationStatus();
285        if (isInstallTask()) {
286            validateInstall(status);
287        }
288        doValidate(status);
289        return status;
290    }
291
292    public abstract void doValidate(ValidationStatus status) throws PackageException;
293
294    protected LocalPackage validateInstall(ValidationStatus status) throws PackageException {
295        LocalPackage oldpkg = service.getActivePackage(pkg.getName());
296        if (oldpkg != null) {
297            if (oldpkg.getPackageState() == PackageState.INSTALLING) {
298                status.addWarning("A package with the same name: " + oldpkg.getId()
299                        + " is being installing. Try again later.");
300            } else {
301                status.addWarning("The package " + oldpkg.getId() + " will be uninstalled!");
302            }
303            return oldpkg;
304        }
305        return null;
306    }
307
308    @Override
309    public String getRelativeFilePath(File file) {
310        String path = file.getAbsolutePath();
311        if (path.startsWith(serverPathPrefix)) {
312            return path.substring(serverPathPrefix.length());
313        }
314        return path;
315    }
316
317}