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}