001/*
002 * (C) Copyright 2006-2010 Nuxeo SAS (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.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
016 */
017package org.nuxeo.runtime.reload;
018
019import java.io.File;
020import java.io.IOException;
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.util.Arrays;
024import java.util.jar.Manifest;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.common.Environment;
029import org.nuxeo.common.utils.FileUtils;
030import org.nuxeo.common.utils.JarUtils;
031import org.nuxeo.common.utils.ZipUtils;
032import org.nuxeo.runtime.RuntimeService;
033import org.nuxeo.runtime.RuntimeServiceException;
034import org.nuxeo.runtime.api.Framework;
035import org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor;
036import org.nuxeo.runtime.model.ComponentContext;
037import org.nuxeo.runtime.model.DefaultComponent;
038import org.nuxeo.runtime.services.event.Event;
039import org.nuxeo.runtime.services.event.EventService;
040import org.osgi.framework.Bundle;
041import org.osgi.framework.BundleContext;
042import org.osgi.framework.BundleException;
043import org.osgi.framework.ServiceReference;
044import org.osgi.service.packageadmin.PackageAdmin;
045
046/**
047 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
048 */
049public class ReloadComponent extends DefaultComponent implements ReloadService {
050
051    private static final Log log = LogFactory.getLog(ReloadComponent.class);
052
053    protected static Bundle bundle;
054
055    protected Long lastFlushed;
056
057    public static BundleContext getBundleContext() {
058        return bundle.getBundleContext();
059    }
060
061    public static Bundle getBundle() {
062        return bundle;
063    }
064
065    @Override
066    public void activate(ComponentContext context) {
067        super.activate(context);
068        bundle = context.getRuntimeContext().getBundle();
069    }
070
071    @Override
072    public void deactivate(ComponentContext context) {
073        super.deactivate(context);
074        bundle = null;
075    }
076
077    @Override
078    public void reload() {
079        if (log.isDebugEnabled()) {
080            log.debug("Starting reload");
081        }
082        try {
083            reloadProperties();
084        } catch (IOException e) {
085            throw new RuntimeServiceException(e);
086        }
087        EventService eventService = Framework.getLocalService(EventService.class);
088        eventService.sendEvent(new Event(RELOAD_TOPIC, RELOAD_EVENT_ID, this, null));
089        if (log.isDebugEnabled()) {
090            log.debug("Reload done");
091        }
092    }
093
094    @Override
095    public void reloadProperties() throws IOException {
096        log.info("Reload runtime properties");
097        Framework.getRuntime().reloadProperties();
098    }
099
100    @Override
101    public void reloadRepository() {
102        log.info("Reload repository");
103        Framework.getLocalService(EventService.class).sendEvent(
104                new Event(RELOAD_TOPIC, RELOAD_REPOSITORIES_ID, this, null));
105    }
106
107    @Override
108    public void reloadSeamComponents() {
109        log.info("Reload Seam components");
110        Framework.getLocalService(EventService.class).sendEvent(
111                new Event(RELOAD_TOPIC, RELOAD_SEAM_EVENT_ID, this, null));
112    }
113
114    @Override
115    public void flush() {
116        if (log.isDebugEnabled()) {
117            log.debug("Starting flush");
118        }
119        flushJaasCache();
120        EventService eventService = Framework.getLocalService(EventService.class);
121        eventService.sendEvent(new Event(RELOAD_TOPIC, FLUSH_EVENT_ID, this, null));
122        setFlushedNow();
123        if (log.isDebugEnabled()) {
124            log.debug("Flush done");
125        }
126    }
127
128    @Override
129    public void flushJaasCache() {
130        log.info("Flush the JAAS cache");
131        EventService eventService = Framework.getLocalService(EventService.class);
132        eventService.sendEvent(new Event("usermanager", "user_changed", this, "Deployer")); // the data argument is
133                                                                                            // optional
134        setFlushedNow();
135    }
136
137    @Override
138    public void flushSeamComponents() {
139        log.info("Flush Seam components");
140        Framework.getLocalService(EventService.class).sendEvent(
141                new Event(RELOAD_TOPIC, FLUSH_SEAM_EVENT_ID, this, null));
142        setFlushedNow();
143    }
144
145    @Override
146    public String deployBundle(File file) throws BundleException {
147        return deployBundle(file, false);
148    }
149
150    @Override
151    public String deployBundle(File file, boolean reloadResourceClasspath) throws BundleException {
152        String name = getOSGIBundleName(file);
153        if (name == null) {
154            log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", file.getAbsolutePath()));
155            return null;
156        }
157
158        String path = file.getAbsolutePath();
159
160        log.info(String.format("Before deploy bundle for file at '%s'\n" + "%s", path, getRuntimeStatus()));
161
162        if (reloadResourceClasspath) {
163            URL url;
164            try {
165                url = new File(path).toURI().toURL();
166            } catch (MalformedURLException e) {
167                throw new RuntimeException(e);
168            }
169            Framework.reloadResourceLoader(Arrays.asList(url), null);
170        }
171
172        // check if this is a bundle first
173        Bundle newBundle = getBundleContext().installBundle(path);
174        if (newBundle == null) {
175            throw new IllegalArgumentException("Could not find a valid bundle at path: " + path);
176        }
177        newBundle.start();
178
179        log.info(String.format("Deploy done for bundle with name '%s'.\n" + "%s", newBundle.getSymbolicName(),
180                getRuntimeStatus()));
181
182        return newBundle.getSymbolicName();
183    }
184
185    @Override
186    public void undeployBundle(File file, boolean reloadResources) throws BundleException {
187        String name = getOSGIBundleName(file);
188        String path = file.getAbsolutePath();
189        if (name == null) {
190            log.error(String.format("No Bundle-SymbolicName found in MANIFEST for jar at '%s'", path));
191            return;
192        }
193
194        undeployBundle(name);
195
196        if (reloadResources) {
197            URL url;
198            try {
199                url = new File(path).toURI().toURL();
200            } catch (MalformedURLException e) {
201                throw new RuntimeException(e);
202            }
203            Framework.reloadResourceLoader(null, Arrays.asList(url));
204        }
205    }
206
207    @Override
208    public void undeployBundle(String bundleName) throws BundleException {
209        if (bundleName == null) {
210            // ignore
211            return;
212        }
213        log.info(String.format("Before undeploy bundle with name '%s'.\n" + "%s", bundleName, getRuntimeStatus()));
214        BundleContext ctx = getBundleContext();
215        ServiceReference ref = ctx.getServiceReference(PackageAdmin.class.getName());
216        PackageAdmin srv = (PackageAdmin) ctx.getService(ref);
217        try {
218            for (Bundle b : srv.getBundles(bundleName, null)) {
219                if (b != null && b.getState() == Bundle.ACTIVE) {
220                    b.stop();
221                    b.uninstall();
222                }
223            }
224        } finally {
225            ctx.ungetService(ref);
226        }
227        log.info(String.format("Undeploy done.\n" + "%s", getRuntimeStatus()));
228    }
229
230    @Override
231    public Long lastFlushed() {
232        return lastFlushed;
233    }
234
235    /**
236     * Sets the last date date to current date timestamp
237     *
238     * @since 5.6
239     */
240    protected void setFlushedNow() {
241        lastFlushed = Long.valueOf(System.currentTimeMillis());
242    }
243
244    /**
245     * @deprecated since 5.6, use {@link #runDeploymentPreprocessor()} instead
246     */
247    @Deprecated
248    public void installWebResources(File file) throws IOException {
249        log.info("Install web resources");
250        if (file.isDirectory()) {
251            File war = new File(file, "web");
252            war = new File(war, "nuxeo.war");
253            if (war.isDirectory()) {
254                FileUtils.copyTree(war, getAppDir());
255            } else {
256                // compatibility mode with studio 1.5 - see NXP-6186
257                war = new File(file, "nuxeo.war");
258                if (war.isDirectory()) {
259                    FileUtils.copyTree(war, getAppDir());
260                }
261            }
262        } else if (file.isFile()) { // a jar
263            File war = getWarDir();
264            ZipUtils.unzip("web/nuxeo.war", file, war);
265            // compatibility mode with studio 1.5 - see NXP-6186
266            ZipUtils.unzip("nuxeo.war", file, war);
267        }
268    }
269
270    public void runDeploymentPreprocessor() throws IOException {
271        if (log.isDebugEnabled()) {
272            log.debug("Start running deployment preprocessor");
273        }
274        String rootPath = Environment.getDefault().getHome().getAbsolutePath();
275        File root = new File(rootPath);
276        DeploymentPreprocessor processor = new DeploymentPreprocessor(root);
277        // initialize
278        processor.init();
279        // and predeploy
280        processor.predeploy();
281        if (log.isDebugEnabled()) {
282            log.debug("Deployment preprocessing done");
283        }
284    }
285
286    protected static File getAppDir() {
287        return Environment.getDefault().getConfig().getParentFile();
288    }
289
290    protected static File getWarDir() {
291        return new File(getAppDir(), "nuxeo.war");
292    }
293
294    @Override
295    public String getOSGIBundleName(File file) {
296        Manifest mf = JarUtils.getManifest(file);
297        if (mf == null) {
298            return null;
299        }
300        String bundleName = mf.getMainAttributes().getValue("Bundle-SymbolicName");
301        if (bundleName == null) {
302            return null;
303        }
304        int index = bundleName.indexOf(';');
305        if (index > -1) {
306            bundleName = bundleName.substring(0, index);
307        }
308        return bundleName;
309    }
310
311    protected String getRuntimeStatus() {
312        StringBuilder msg = new StringBuilder();
313        RuntimeService runtime = Framework.getRuntime();
314        runtime.getStatusMessage(msg);
315        return msg.toString();
316    }
317
318}