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.tomcat.dev;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.lang.reflect.Method;
023import java.net.URL;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Timer;
029import java.util.TimerTask;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.osgi.application.FrameworkBootstrap;
034import org.nuxeo.osgi.application.MutableClassLoader;
035
036/**
037 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
038 */
039public class DevFrameworkBootstrap extends FrameworkBootstrap implements DevBundlesManager {
040
041    protected final Log log = LogFactory.getLog(DevFrameworkBootstrap.class);
042
043    protected DevBundle[] devBundles;
044
045    protected Timer bundlesCheck;
046
047    protected long lastModified = 0;
048
049    protected ReloadServiceInvoker reloadServiceInvoker;
050
051    protected File devBundlesFile;
052
053    protected final File seamdev;
054
055    protected final File webclasses;
056
057    public DevFrameworkBootstrap(MutableClassLoader cl, File home) throws IOException {
058        super(cl, home);
059        devBundlesFile = new File(home, "dev.bundles");
060        seamdev = new File(home, "nuxeo.war/WEB-INF/dev");
061        webclasses = new File(home, "nuxeo.war/WEB-INF/classes");
062    }
063
064    @Override
065    public void start() throws ReflectiveOperationException, IOException {
066        // check if we have dev. bundles or libs to deploy and add them to the
067        // classpath
068        preloadDevBundles();
069        // start the framework
070        super.start();
071        reloadServiceInvoker = new ReloadServiceInvoker((ClassLoader) loader);
072        writeComponentIndex();
073        postloadDevBundles(); // start dev bundles if any
074        String installReloadTimerOption = (String) env.get(INSTALL_RELOAD_TIMER);
075        if (installReloadTimerOption != null && Boolean.parseBoolean(installReloadTimerOption) == Boolean.TRUE) {
076            toggleTimer();
077        }
078    }
079
080    @Override
081    public void toggleTimer() {
082        // start reload timer
083        if (bundlesCheck != null) {
084            bundlesCheck.cancel();
085            bundlesCheck = null;
086        } else {
087            bundlesCheck = new Timer("Dev Bundles Loader");
088            bundlesCheck.scheduleAtFixedRate(new TimerTask() {
089                @Override
090                public void run() {
091                    loadDevBundles();
092                }
093            }, 2000, 2000);
094        }
095    }
096
097    @Override
098    public boolean isTimerRunning() {
099        return bundlesCheck != null;
100    }
101
102    @Override
103    public void stop() throws ReflectiveOperationException {
104        if (bundlesCheck != null) {
105            bundlesCheck.cancel();
106            bundlesCheck = null;
107        }
108        super.stop();
109    }
110
111    @Override
112    public String getDevBundlesLocation() {
113        return devBundlesFile.getAbsolutePath();
114    }
115
116    /**
117     * Load the development bundles and libs if any in the classpath before starting the framework.
118     */
119    protected void preloadDevBundles() throws IOException {
120        if (!devBundlesFile.isFile()) {
121            return;
122        }
123        lastModified = devBundlesFile.lastModified();
124        devBundles = DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile));
125        if (devBundles.length == 0) {
126            devBundles = null;
127            return;
128        }
129        installNewClassLoader(devBundles);
130    }
131
132    protected void postloadDevBundles() throws ReflectiveOperationException {
133        if (devBundles != null) {
134            reloadServiceInvoker.hotDeployBundles(devBundles);
135        }
136    }
137
138    @Override
139    public void loadDevBundles() {
140        long tm = devBundlesFile.lastModified();
141        if (lastModified >= tm) {
142            return;
143        }
144        lastModified = tm;
145        try {
146            reloadDevBundles(DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile)));
147        } catch (ReflectiveOperationException | IOException e) {
148            log.error("Failed to deploy dev bundles", e);
149        }
150    }
151
152    @Override
153    public void resetDevBundles(String path) {
154        devBundlesFile = new File(path);
155        lastModified = 0;
156        loadDevBundles();
157    }
158
159    @Override
160    public DevBundle[] getDevBundles() {
161        return devBundles;
162    }
163
164    protected synchronized void reloadDevBundles(DevBundle[] bundles) throws ReflectiveOperationException {
165
166        if (devBundles != null) { // clear last context
167            try {
168                reloadServiceInvoker.hotUndeployBundles(devBundles);
169                clearClassLoader();
170            } finally {
171                devBundles = null;
172            }
173        }
174
175        if (bundles != null) { // create new context
176            try {
177                installNewClassLoader(bundles);
178                reloadServiceInvoker.hotDeployBundles(bundles);
179            } finally {
180                devBundles = bundles;
181            }
182        }
183    }
184
185    protected void clearClassLoader() {
186        NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader;
187        devLoader.clear();
188        System.gc();
189    }
190
191    protected void installNewClassLoader(DevBundle[] bundles) {
192        List<URL> jarUrls = new ArrayList<URL>();
193        List<File> seamDirs = new ArrayList<File>();
194        List<File> resourceBundleFragments = new ArrayList<File>();
195        // filter dev bundles types
196        for (DevBundle bundle : bundles) {
197            if (bundle.devBundleType.isJar) {
198                try {
199                    jarUrls.add(bundle.url());
200                } catch (IOException e) {
201                    log.error("Cannot install " + bundle);
202                }
203            } else if (bundle.devBundleType == DevBundleType.Seam) {
204                seamDirs.add(bundle.file());
205            } else if (bundle.devBundleType == DevBundleType.ResourceBundleFragment) {
206                resourceBundleFragments.add(bundle.file());
207            }
208        }
209
210        // install class loader
211        NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader;
212        devLoader.createLocalClassLoader(jarUrls.toArray(new URL[jarUrls.size()]));
213
214        // install seam classes in hot sync folder
215        try {
216            installSeamClasses(seamDirs.toArray(new File[seamDirs.size()]));
217        } catch (IOException e) {
218            log.error("Cannot install seam classes in hotsync folder", e);
219        }
220
221        // install l10n resources
222        try {
223            installResourceBundleFragments(resourceBundleFragments);
224        } catch (IOException e) {
225            log.error("Cannot install l10n resources", e);
226        }
227    }
228
229    public void writeComponentIndex() {
230        File file = new File(home.getParentFile(), "sdk");
231        file.mkdirs();
232        file = new File(file, "components.index");
233        // if (file.isFile()) {
234        // return;
235        // }
236        try {
237            Method m = getClassLoader().loadClass("org.nuxeo.runtime.model.impl.ComponentRegistrySerializer").getMethod(
238                    "writeIndex", File.class);
239            m.invoke(null, file);
240        } catch (ReflectiveOperationException t) {
241            // ignore
242        }
243    }
244
245    public void installSeamClasses(File[] dirs) throws IOException {
246        if (seamdev.exists()) {
247            IOUtils.deleteTree(seamdev);
248        }
249        seamdev.mkdirs();
250        for (File dir : dirs) {
251            IOUtils.copyTree(dir, seamdev);
252        }
253    }
254
255    public void installResourceBundleFragments(List<File> files) throws IOException {
256        Map<String, List<File>> fragments = new HashMap<String, List<File>>();
257
258        for (File file : files) {
259            String name = resourceBundleName(file);
260            if (!fragments.containsKey(name)) {
261                fragments.put(name, new ArrayList<File>());
262            }
263            fragments.get(name).add(file);
264        }
265        for (String name : fragments.keySet()) {
266            IOUtils.appendResourceBundleFragments(name, fragments.get(name), webclasses);
267        }
268    }
269
270    protected static String resourceBundleName(File file) {
271        String name = file.getName();
272        return name.substring(name.lastIndexOf('-') + 1);
273    }
274
275}