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