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