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, jcarsique 016 */ 017package org.nuxeo.osgi.application; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.lang.reflect.Method; 026import java.util.ArrayList; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Properties; 032import java.util.jar.JarEntry; 033import java.util.jar.JarFile; 034import java.util.zip.ZipEntry; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038 039/** 040 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 041 */ 042public class FrameworkBootstrap implements LoaderConstants { 043 044 protected static final String DEFAULT_BUNDLES_CP = "bundles/*:plugins/*"; 045 046 protected static final String DEFAULT_LIBS_CP = "lib/*:.:config"; 047 048 private static final Log log = LogFactory.getLog(FrameworkBootstrap.class); 049 050 protected File home; 051 052 protected MutableClassLoader loader; 053 054 protected Map<String, Object> env; 055 056 protected Class<?> frameworkLoaderClass; 057 058 protected long startTime; 059 060 protected boolean scanForNestedJars = true; 061 062 protected boolean flushCache = false; 063 064 public FrameworkBootstrap(ClassLoader cl, File home) throws IOException { 065 this(new MutableClassLoaderDelegate(cl), home); 066 } 067 068 public FrameworkBootstrap(MutableClassLoader loader, File home) throws IOException { 069 this.home = home.getCanonicalFile(); 070 this.loader = loader; 071 initializeEnvironment(); 072 } 073 074 public void setHostName(String value) { 075 env.put(HOST_NAME, value); 076 } 077 078 public void setHostVersion(String value) { 079 env.put(HOST_VERSION, value); 080 } 081 082 public void setDoPreprocessing(boolean doPreprocessing) { 083 env.put(PREPROCESSING, Boolean.toString(doPreprocessing)); 084 } 085 086 public void setDevMode(String devMode) { 087 env.put(DEVMODE, devMode); 088 } 089 090 public void setFlushCache(boolean flushCache) { 091 this.flushCache = flushCache; 092 } 093 094 public void setScanForNestedJars(boolean scanForNestedJars) { 095 this.scanForNestedJars = scanForNestedJars; 096 } 097 098 public Map<String, Object> env() { 099 return env; 100 } 101 102 public MutableClassLoader getLoader() { 103 return loader; 104 } 105 106 public ClassLoader getClassLoader() { 107 return loader.getClassLoader(); 108 } 109 110 public File getHome() { 111 return home; 112 } 113 114 public void initialize() throws ReflectiveOperationException, IOException { 115 startTime = System.currentTimeMillis(); 116 List<File> bundleFiles = buildClassPath(); 117 frameworkLoaderClass = getClassLoader().loadClass("org.nuxeo.osgi.application.loader.FrameworkLoader"); 118 Method init = frameworkLoaderClass.getMethod("initialize", ClassLoader.class, File.class, List.class, Map.class); 119 init.invoke(null, loader.getClassLoader(), home, bundleFiles, env); 120 } 121 122 public void start() throws ReflectiveOperationException, IOException { 123 if (frameworkLoaderClass == null) { 124 throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first"); 125 } 126 Method start = frameworkLoaderClass.getMethod("start"); 127 start.invoke(null); 128 printStartedMessage(); 129 } 130 131 public void stop() throws ReflectiveOperationException { 132 if (frameworkLoaderClass == null) { 133 throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first"); 134 } 135 Method stop = frameworkLoaderClass.getMethod("stop"); 136 stop.invoke(null); 137 } 138 139 public String installBundle(File f) throws ReflectiveOperationException { 140 if (frameworkLoaderClass == null) { 141 throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first"); 142 } 143 Method install = frameworkLoaderClass.getMethod("install", File.class); 144 return (String) install.invoke(null, f); 145 } 146 147 public void uninstallBundle(String name) throws ReflectiveOperationException { 148 if (frameworkLoaderClass == null) { 149 throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first"); 150 } 151 Method uninstall = frameworkLoaderClass.getMethod("uninstall", String.class); 152 uninstall.invoke(null, name); 153 } 154 155 @SuppressWarnings({ "unchecked", "rawtypes" }) 156 protected void initializeEnvironment() throws IOException { 157 System.setProperty(HOME_DIR, home.getAbsolutePath()); 158 env = new HashMap<String, Object>(); 159 // initialize with default values 160 env.put(BUNDLES, DEFAULT_BUNDLES_CP); 161 env.put(LIBS, DEFAULT_LIBS_CP); 162 // load launcher.properties file if exists to overwrite default values 163 File file = new File(home, "launcher.properties"); 164 if (!file.isFile()) { 165 return; 166 } 167 Properties p = new Properties(); 168 FileInputStream in = new FileInputStream(file); 169 try { 170 p.load(in); 171 env.putAll((Map) p); 172 String v = (String) env.get(SCAN_FOR_NESTED_JARS); 173 if (v != null) { 174 scanForNestedJars = Boolean.parseBoolean(v); 175 } 176 v = (String) env.get(FLUSH_CACHE); 177 if (v != null) { 178 flushCache = Boolean.parseBoolean(v); 179 } 180 } finally { 181 in.close(); 182 } 183 } 184 185 protected void printStartedMessage() { 186 log.info("Framework started in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec."); 187 } 188 189 protected File newFile(String path) throws IOException { 190 if (path.startsWith("/")) { 191 return new File(path).getCanonicalFile(); 192 } else { 193 return new File(home, path).getCanonicalFile(); 194 } 195 } 196 197 /** 198 * Fills the classloader with all jars found in the defined classpath. 199 * 200 * @return the list of bundle files. 201 */ 202 protected List<File> buildClassPath() throws IOException { 203 List<File> bundleFiles = new ArrayList<File>(); 204 String libsCp = (String) env.get(LIBS); 205 if (libsCp != null) { 206 buildLibsClassPath(libsCp); 207 } 208 String bundlesCp = (String) env.get(BUNDLES); 209 if (bundlesCp != null) { 210 buildBundlesClassPath(bundlesCp, bundleFiles); 211 } 212 extractNestedJars(bundleFiles, new File(home, "tmp/nested-jars")); 213 return bundleFiles; 214 } 215 216 protected void buildLibsClassPath(String libsCp) throws IOException { 217 String[] ar = libsCp.split(":"); 218 for (String entry : ar) { 219 File entryFile; 220 if (entry.endsWith("/*")) { 221 entryFile = newFile(entry.substring(0, entry.length() - 2)); 222 File[] files = entryFile.listFiles(); 223 if (files != null) { 224 for (File file : files) { 225 loader.addURL(file.toURI().toURL()); 226 } 227 } 228 } else { 229 entryFile = newFile(entry); 230 loader.addURL(entryFile.toURI().toURL()); 231 } 232 } 233 } 234 235 protected void buildBundlesClassPath(String bundlesCp, List<File> bundleFiles) throws IOException { 236 String[] ar = bundlesCp.split(":"); 237 for (String entry : ar) { 238 File entryFile; 239 if (entry.endsWith("/*")) { 240 entryFile = newFile(entry.substring(0, entry.length() - 2)); 241 File[] files = entryFile.listFiles(); 242 if (files != null) { 243 for (File file : files) { 244 String path = file.getPath(); 245 if (path.endsWith(".jar") || path.endsWith(".zip") || path.endsWith(".war") 246 || path.endsWith("rar")) { 247 bundleFiles.add(file); 248 loader.addURL(file.toURI().toURL()); 249 } 250 } 251 } 252 } else { 253 entryFile = newFile(entry); 254 bundleFiles.add(entryFile); 255 loader.addURL(entryFile.toURI().toURL()); 256 } 257 } 258 } 259 260 protected void extractNestedJars(List<File> bundleFiles, File dir) throws IOException { 261 if (!scanForNestedJars) { 262 return; 263 } 264 if (dir.isDirectory()) { 265 if (flushCache) { 266 deleteAll(dir); 267 } else { 268 File[] files = dir.listFiles(); 269 if (files != null) { 270 for (File f : files) { 271 loader.addURL(f.toURI().toURL()); 272 } 273 } 274 return; 275 } 276 } 277 dir.mkdirs(); 278 for (File f : bundleFiles) { 279 if (f.isFile()) { 280 extractNestedJars(f, dir); 281 } 282 } 283 } 284 285 protected void extractNestedJars(File file, File tmpDir) throws IOException { 286 JarFile jarFile = new JarFile(file); 287 String fileName = file.getName(); 288 Enumeration<JarEntry> entries = jarFile.entries(); 289 while (entries.hasMoreElements()) { 290 JarEntry entry = entries.nextElement(); 291 String path = entry.getName(); 292 if (entry.getName().endsWith(".jar")) { 293 String name = path.replace('/', '_'); 294 File dest = new File(tmpDir, fileName + '-' + name); 295 extractNestedJar(jarFile, entry, dest); 296 loader.addURL(dest.toURI().toURL()); 297 } 298 } 299 } 300 301 protected void extractNestedJar(JarFile file, ZipEntry entry, File dest) throws IOException { 302 InputStream in = null; 303 try { 304 in = file.getInputStream(entry); 305 copyToFile(in, dest); 306 } finally { 307 if (in != null) { 308 in.close(); 309 } 310 } 311 } 312 313 public static void deleteAll(File file) { 314 if (file.isDirectory()) { 315 File[] files = file.listFiles(); 316 if (files != null) { 317 for (File f : files) { 318 deleteAll(f); 319 } 320 } 321 } 322 file.delete(); 323 } 324 325 public static void copyFile(File src, File file) throws IOException { 326 FileInputStream in = new FileInputStream(src); 327 try { 328 copyToFile(in, file); 329 } finally { 330 in.close(); 331 } 332 } 333 334 public static void copyToFile(InputStream in, File file) throws IOException { 335 OutputStream out = null; 336 try { 337 out = new FileOutputStream(file); 338 byte[] buffer = createBuffer(in.available()); 339 int read; 340 while ((read = in.read(buffer)) != -1) { 341 out.write(buffer, 0, read); 342 } 343 } finally { 344 if (out != null) { 345 out.close(); 346 } 347 } 348 } 349 350 private static final int BUFFER_SIZE = 1024 * 64; // 64K 351 352 private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K 353 354 private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K 355 356 private static byte[] createBuffer(int preferredSize) { 357 if (preferredSize < 1) { 358 preferredSize = BUFFER_SIZE; 359 } 360 if (preferredSize > MAX_BUFFER_SIZE) { 361 preferredSize = MAX_BUFFER_SIZE; 362 } else if (preferredSize < MIN_BUFFER_SIZE) { 363 preferredSize = MIN_BUFFER_SIZE; 364 } 365 return new byte[preferredSize]; 366 } 367 368 public static File findFileStartingWidth(File dir, String prefix) { 369 String[] names = dir.list(); 370 if (names != null) { 371 for (String name : names) { 372 if (name.startsWith(prefix)) { 373 return new File(dir, name); 374 } 375 } 376 } 377 return null; 378 } 379 380 public static void copyTree(File src, File dst) throws IOException { 381 if (src.isFile()) { 382 copyFile(src, dst); 383 } else if (src.isDirectory()) { 384 if (dst.exists()) { 385 dst = new File(dst, src.getName()); 386 dst.mkdir(); 387 } else { // allows renaming dest dir 388 dst.mkdirs(); 389 } 390 File[] files = src.listFiles(); 391 for (File file : files) { 392 copyTree(file, dst); 393 } 394 } 395 } 396 397}