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 static java.nio.charset.StandardCharsets.UTF_8;
022
023import java.io.ByteArrayOutputStream;
024import java.io.File;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.PrintStream;
028import java.text.SimpleDateFormat;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.Map;
032import java.util.Properties;
033
034import org.apache.commons.collections.MapUtils;
035import org.apache.commons.io.FileUtils;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.common.Environment;
039import org.nuxeo.common.utils.StringUtils;
040import org.nuxeo.connect.update.LocalPackage;
041import org.nuxeo.connect.update.PackageException;
042import org.nuxeo.connect.update.PackageState;
043import org.nuxeo.connect.update.PackageUpdateService;
044import org.nuxeo.connect.update.ValidationStatus;
045import org.nuxeo.connect.update.task.Task;
046import org.nuxeo.connect.update.task.update.UpdateManager;
047
048/**
049 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
050 */
051public abstract class AbstractTask implements Task {
052
053    static final Log log = LogFactory.getLog(AbstractTask.class);
054
055    public static final String PKG_ID = "package.id";
056
057    public static final String PKG_NAME = "package.name";
058
059    public static final String PKG_VERSION = "package.version";
060
061    public static final String PKG_ROOT = "package.root";
062
063    public static final String ENV_HOME = "env.home";
064
065    /**
066     * @since 5.5
067     */
068    public static final String ENV_SERVER_HOME = "env.server.home";
069
070    /**
071     * Set only on JBoss - the EAR root directory path
072     */
073    public static final String ENV_EAR = "env.ear";
074
075    public static final String ENV_LIB = "env.lib";
076
077    public static final String ENV_SYSLIB = "env.syslib";
078
079    public static final String ENV_BUNDLES = "env.bundles";
080
081    public static final String ENV_CONFIG = "env.config";
082
083    /**
084     * @since 5.5
085     */
086    public static final String ENV_TEMPLATES = "env.templates";
087
088    public static final String ENV_TIMESTAMP = "sys.timestamp";
089
090    /**
091     * The host application name.
092     *
093     * @see Environment#getHostApplicationName()
094     */
095    public static final String ENV_HOSTAPP_NAME = "env.hostapp.name";
096
097    /**
098     * The host application version
099     *
100     * @see Environment#getHostApplicationVersion()
101     */
102    public static final String ENV_HOSTAPP_VERSION = "env.hostapp.version";
103
104    protected boolean restart;
105
106    protected LocalPackage pkg;
107
108    protected String serverPathPrefix;
109
110    protected UpdateManager updateMgr;
111
112    protected boolean updateMgrLoaded = false;
113
114    protected PackageUpdateService service;
115
116    /**
117     * A map of environment key/values that can be used in XML install files as variables.
118     */
119    protected final Map<String, String> env;
120
121    public AbstractTask(PackageUpdateService pus) {
122        service = pus;
123        env = new HashMap<>();
124        Environment nxenv = Environment.getDefault();
125        File serverHome = nxenv.getServerHome();
126        File nxHome = nxenv.getRuntimeHome();
127        File config = nxenv.getConfig();
128        serverPathPrefix = serverHome.getAbsolutePath();
129        if (!serverPathPrefix.endsWith(File.separator)) {
130            serverPathPrefix = serverPathPrefix.concat(File.separator);
131        }
132        env.put(ENV_SERVER_HOME, serverHome.getAbsolutePath());
133        env.put(ENV_HOME, nxHome.getAbsolutePath());
134        env.put(ENV_CONFIG, config.getAbsolutePath());
135        env.put(ENV_HOSTAPP_NAME, nxenv.getHostApplicationName());
136        env.put(ENV_HOSTAPP_VERSION, nxenv.getHostApplicationVersion());
137        env.put(ENV_SYSLIB, new File(serverHome, "lib").getAbsolutePath());
138        if (nxenv.isJBoss()) {
139            File ear = config.getParentFile();
140            env.put(ENV_EAR, ear.getAbsolutePath());
141            env.put(ENV_LIB, new File(ear, "lib").getAbsolutePath());
142            env.put(ENV_BUNDLES, new File(ear, "bundles").getAbsolutePath());
143        } else {
144            env.put(ENV_LIB, new File(nxHome, "lib").getAbsolutePath());
145            env.put(ENV_BUNDLES, new File(nxHome, "bundles").getAbsolutePath());
146        }
147        env.put(ENV_TEMPLATES, new File(serverHome, "templates").getAbsolutePath());
148        env.put(ENV_TIMESTAMP, new SimpleDateFormat("yyMMddHHmmss").format(new Date()));
149        updateMgr = new UpdateManager(serverHome, service.getRegistry());
150    }
151
152    public abstract boolean isInstallTask();
153
154    @Override
155    @SuppressWarnings("hiding")
156    public void initialize(LocalPackage pkg, boolean restart) throws PackageException {
157        this.pkg = pkg;
158        this.restart = restart;
159        env.put(PKG_ID, pkg.getId());
160        env.put(PKG_NAME, pkg.getName());
161        env.put(PKG_VERSION, pkg.getVersion().toString());
162        env.put(PKG_ROOT, pkg.getData().getRoot().getAbsolutePath());
163
164        if (log.isDebugEnabled()) {
165            final ByteArrayOutputStream out = new ByteArrayOutputStream();
166            final PrintStream outPrint = new PrintStream(out);
167            MapUtils.debugPrint(outPrint, null, env);
168            log.debug(out.toString());
169        }
170    }
171
172    /**
173     * Get a file given its key in the environment map. If no key exists then null is returned.
174     *
175     * @param key
176     */
177    public File getFile(String key) {
178        String val = env.get(key);
179        return val == null ? null : new File(val);
180    }
181
182    @Override
183    public boolean isRestartRequired() {
184        return restart;
185    }
186
187    @Override
188    public LocalPackage getPackage() {
189        return pkg;
190    }
191
192    protected Map<Object, Object> createContextMap(Map<String, String> params) {
193        Map<Object, Object> map = new HashMap<>(System.getProperties());
194        map.putAll(env);
195        if (params != null && !params.isEmpty()) {
196            map.putAll(params);
197        }
198        return map;
199    }
200
201    protected String loadParametrizedFile(File file, Map<String, String> params) throws IOException {
202        String content = FileUtils.readFileToString(file, UTF_8);
203        // replace variables.
204        return StringUtils.expandVars(content, createContextMap(params));
205    }
206
207    protected void saveParams(Map<String, String> params) throws PackageException {
208        if (params == null || params.isEmpty()) {
209            return;
210        }
211        try {
212            Properties props = new Properties();
213            props.putAll(params);
214            File file = pkg.getData().getEntry(LocalPackage.INSTALL_PROPERTIES);
215            try (FileOutputStream out = new FileOutputStream(file)) {
216                props.store(out, "user install parameters");
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<>());
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}