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