/* * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * bstefanescu */ package org.nuxeo.runtime.tomcat.dev; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.osgi.application.FrameworkBootstrap; import org.nuxeo.osgi.application.MutableClassLoader; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class DevFrameworkBootstrap extends FrameworkBootstrap implements DevBundlesManager { public static final String DEV_BUNDLES_NAME = "org.nuxeo:type=sdk,name=dev-bundles"; public static final String WEB_RESOURCES_NAME = "org.nuxeo:type=sdk,name=web-resources"; protected final Log log = LogFactory.getLog(DevFrameworkBootstrap.class); protected DevBundle[] devBundles; protected Timer bundlesCheck; protected long lastModified = 0; protected ReloadServiceInvoker reloadServiceInvoker; protected File devBundlesFile; protected final File seamdev; protected final File webclasses; public DevFrameworkBootstrap(MutableClassLoader cl, File home) throws IOException { super(cl, home); devBundlesFile = new File(home, "dev.bundles"); seamdev = new File(home, "nuxeo.war/WEB-INF/dev"); webclasses = new File(home, "nuxeo.war/WEB-INF/classes"); } @Override public void start(MutableClassLoader cl) throws ReflectiveOperationException, IOException, JMException { // check if we have dev. bundles or libs to deploy and add them to the // classpath preloadDevBundles(); // start the framework super.start(cl); reloadServiceInvoker = new ReloadServiceInvoker((ClassLoader) loader); writeComponentIndex(); postloadDevBundles(); // start dev bundles if any String installReloadTimerOption = (String) env.get(INSTALL_RELOAD_TIMER); if (installReloadTimerOption != null && Boolean.parseBoolean(installReloadTimerOption) == Boolean.TRUE) { toggleTimer(); } MBeanServer server = ManagementFactory.getPlatformMBeanServer(); server.registerMBean(this, new ObjectName(DEV_BUNDLES_NAME)); server.registerMBean(cl, new ObjectName(WEB_RESOURCES_NAME)); } @Override public void toggleTimer() { // start reload timer if (bundlesCheck != null) { bundlesCheck.cancel(); bundlesCheck = null; } else { bundlesCheck = new Timer("Dev Bundles Loader"); bundlesCheck.scheduleAtFixedRate(new TimerTask() { @Override public void run() { loadDevBundles(); } }, 2000, 2000); } } @Override public boolean isTimerRunning() { return bundlesCheck != null; } @Override public void stop(MutableClassLoader cl) throws ReflectiveOperationException, JMException { if (bundlesCheck != null) { bundlesCheck.cancel(); bundlesCheck = null; } try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); server.unregisterMBean(new ObjectName(DEV_BUNDLES_NAME)); server.unregisterMBean(new ObjectName(WEB_RESOURCES_NAME)); } finally { super.stop(cl); } } @Override public String getDevBundlesLocation() { return devBundlesFile.getAbsolutePath(); } /** * Load the development bundles and libs if any in the classpath before starting the framework. */ protected void preloadDevBundles() throws IOException { if (!devBundlesFile.isFile()) { return; } lastModified = devBundlesFile.lastModified(); devBundles = DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile)); if (devBundles.length == 0) { devBundles = null; return; } installNewClassLoader(devBundles); } protected void postloadDevBundles() throws ReflectiveOperationException { if (devBundles != null) { reloadServiceInvoker.hotDeployBundles(devBundles); } } @Override public void loadDevBundles() { long tm = devBundlesFile.lastModified(); if (lastModified >= tm) { return; } lastModified = tm; try { reloadDevBundles(DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile))); } catch (ReflectiveOperationException | IOException e) { log.error("Failed to deploy dev bundles", e); } } @Override public void resetDevBundles(String path) { devBundlesFile = new File(path); lastModified = 0; loadDevBundles(); } @Override public DevBundle[] getDevBundles() { return devBundles; } protected synchronized void reloadDevBundles(DevBundle[] bundles) throws ReflectiveOperationException { if (devBundles != null) { // clear last context try { reloadServiceInvoker.hotUndeployBundles(devBundles); clearClassLoader(); } finally { devBundles = null; } } if (bundles != null) { // create new context try { installNewClassLoader(bundles); reloadServiceInvoker.hotDeployBundles(bundles); } finally { devBundles = bundles; } } } protected void clearClassLoader() { NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader; devLoader.clear(); System.gc(); } protected void installNewClassLoader(DevBundle[] bundles) { List<URL> jarUrls = new ArrayList<URL>(); List<File> seamDirs = new ArrayList<File>(); List<File> resourceBundleFragments = new ArrayList<File>(); // filter dev bundles types for (DevBundle bundle : bundles) { if (bundle.devBundleType.isJar) { try { jarUrls.add(bundle.url()); } catch (IOException e) { log.error("Cannot install " + bundle); } } else if (bundle.devBundleType == DevBundleType.Seam) { seamDirs.add(bundle.file()); } else if (bundle.devBundleType == DevBundleType.ResourceBundleFragment) { resourceBundleFragments.add(bundle.file()); } } // install class loader NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader; devLoader.createLocalClassLoader(jarUrls.toArray(new URL[jarUrls.size()])); // install seam classes in hot sync folder try { installSeamClasses(seamDirs.toArray(new File[seamDirs.size()])); } catch (IOException e) { log.error("Cannot install seam classes in hotsync folder", e); } // install l10n resources try { installResourceBundleFragments(resourceBundleFragments); } catch (IOException e) { log.error("Cannot install l10n resources", e); } } public void writeComponentIndex() { File file = new File(home.getParentFile(), "sdk"); file.mkdirs(); file = new File(file, "components.index"); // if (file.isFile()) { // return; // } try { Method m = getClassLoader().loadClass("org.nuxeo.runtime.model.impl.ComponentRegistrySerializer") .getMethod("writeIndex", File.class); m.invoke(null, file); } catch (ReflectiveOperationException t) { // ignore } } public void installSeamClasses(File[] dirs) throws IOException { if (seamdev.exists()) { IOUtils.deleteTree(seamdev); } seamdev.mkdirs(); for (File dir : dirs) { IOUtils.copyTree(dir, seamdev); } } public void installResourceBundleFragments(List<File> files) throws IOException { Map<String, List<File>> fragments = new HashMap<String, List<File>>(); for (File file : files) { String name = resourceBundleName(file); if (!fragments.containsKey(name)) { fragments.put(name, new ArrayList<File>()); } fragments.get(name).add(file); } for (String name : fragments.keySet()) { IOUtils.appendResourceBundleFragments(name, fragments.get(name), webclasses); } } protected static String resourceBundleName(File file) { String name = file.getName(); return name.substring(name.lastIndexOf('-') + 1); } }