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