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