001/* 002 * (C) Copyright 2006-2016 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 * 019 */ 020 021package org.nuxeo.osgi.application; 022 023import java.io.File; 024import java.io.IOException; 025import java.net.MalformedURLException; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URL; 029import java.util.ArrayList; 030import java.util.List; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.Environment; 035import org.nuxeo.common.utils.StringUtils; 036import org.nuxeo.osgi.BundleFile; 037import org.nuxeo.osgi.BundleImpl; 038import org.nuxeo.osgi.DirectoryBundleFile; 039import org.nuxeo.osgi.JarBundleFile; 040import org.nuxeo.osgi.OSGiAdapter; 041import org.nuxeo.osgi.SystemBundle; 042import org.osgi.framework.BundleException; 043import org.osgi.framework.FrameworkEvent; 044 045/** 046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 047 */ 048public class StandaloneApplication extends OSGiAdapter { 049 050 public static final String MAIN_TASK = "org.nuxeo.osgi.application.main.task"; 051 052 private static final Log log = LogFactory.getLog(StandaloneApplication.class); 053 054 private static StandaloneApplication instance; 055 056 private static CommandLineOptions options; // TODO should be remove 057 058 private static String[] args; 059 060 private static Runnable mainTask; 061 062 protected final SharedClassLoader classLoader; 063 064 protected final Environment env; 065 066 protected boolean isStarted; 067 068 protected File home; 069 070 protected List<File> classPath; 071 072 protected boolean scanForNestedJARs = true; // by default true 073 074 // a list of class path prefixes that contains JARS that should not be 075 // treated as bundles. 076 protected String[] libdirs; 077 078 public static StandaloneApplication getInstance() { 079 return instance; 080 } 081 082 public static StandaloneApplication createInstance(SharedClassLoader cl) throws IOException { 083 if (instance != null) { 084 throw new IllegalStateException("Application already instantiated"); 085 } 086 // create application environment 087 Environment env = createEnvironment(); 088 Environment.setDefault(env); 089 instance = new StandaloneApplication(cl, env); 090 String val = options.getOption("scanForNestedJARs"); 091 if (val != null) { 092 StandaloneApplication.instance.scanForNestedJARs = Boolean.parseBoolean(val); 093 } 094 // hack to avoid deploying all jars in classpath as bundles 095 String javaLibsProp = System.getProperty("org.nuxeo.launcher.libdirs"); 096 if (javaLibsProp != null) { 097 String[] ar = StringUtils.split(javaLibsProp, ',', false); 098 if (ar.length > 0) { 099 instance.libdirs = ar; 100 File wd = instance.getWorkingDir(); 101 for (int i = 0; i < ar.length; i++) { 102 if (!ar[i].startsWith("/")) { 103 instance.libdirs[i] = new File(wd, ar[i]).getCanonicalFile().getAbsolutePath(); 104 } 105 } 106 } 107 } 108 // end hack 109 return instance; 110 } 111 112 private StandaloneApplication(SharedClassLoader cl, Environment env) { 113 super(env.getHome(), env.getData(), env.getProperties()); 114 classLoader = cl; 115 this.env = env; 116 } 117 118 public SharedClassLoader getSharedClassLoader() { 119 return classLoader; 120 } 121 122 public Environment getEnvironment() { 123 return env; 124 } 125 126 public void start() throws IOException, BundleException { 127 if (isStarted) { 128 throw new IllegalStateException("OSGi Application is already started"); 129 } 130 List<BundleFile> preBundles = loadUserBundles("pre-bundles"); 131 List<BundleFile> postBundles = loadUserBundles("post-bundles"); 132 // start level 1 133 // start bundles that are specified in the osgi.bundles property 134 if (preBundles != null) { 135 startBundles(preBundles); 136 } 137 // start level 2 138 // if needed install all discovered bundles (the one that are located in 139 // bundles dir) 140 autoInstallBundles(); 141 // start level 3 142 if (postBundles != null) { 143 startBundles(postBundles); 144 } 145 fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, getSystemBundle(), null)); 146 isStarted = true; 147 } 148 149 public boolean isStarted() { 150 return isStarted; 151 } 152 153 @Override 154 public void shutdown() throws IOException { 155 if (!isStarted) { 156 throw new IllegalStateException("OSGi Application was not started"); 157 } 158 try { 159 super.shutdown(); 160 } finally { 161 isStarted = false; 162 } 163 } 164 165 protected void startBundles(List<BundleFile> bundles) throws BundleException { 166 for (BundleFile bf : bundles) { 167 this.install(new BundleImpl(this, bf, classLoader.getLoader())); 168 } 169 } 170 171 protected List<BundleFile> loadUserBundles(String key) throws IOException { 172 if (options == null) { 173 return null; 174 } 175 String bundlesString = options.getOption(key); 176 if (bundlesString == null) { 177 return null; // no bundles to load 178 } 179 List<BundleFile> bundles = new ArrayList<>(); 180 String[] ar = StringUtils.split(bundlesString, ':', true); 181 for (String entry : ar) { 182 File file; 183 if (entry.contains("file:")) { 184 try { 185 URL url = new URL(entry); 186 file = new File(url.toURI()); 187 } catch (MalformedURLException e) { 188 throw new IOException(e); 189 } catch (URISyntaxException e) { 190 throw new IOException(e); 191 } 192 } else { 193 file = new File(entry); 194 } 195 BundleFile bf; 196 if (file.isDirectory()) { 197 bf = new DirectoryBundleFile(file); 198 } else { 199 bf = new JarBundleFile(file); 200 } 201 classLoader.addURL(bf.getURL()); 202 bundles.add(bf); 203 } 204 return bundles; 205 } 206 207 public List<File> getClassPath() { 208 return classPath; 209 } 210 211 public void setClassPath(List<File> classPath) { 212 this.classPath = classPath; 213 } 214 215 protected void autoInstallBundles() throws IOException, BundleException { 216 List<File> cp = getClassPath(); 217 if (cp == null || cp.isEmpty()) { 218 return; 219 } 220 boolean clear = hasCommandLineOption("clear"); 221 ClassPath cpath = new ClassPath(classLoader, new File(env.getData(), "nested-jars")); 222 File cache = new File(env.getData(), "bundles.cache"); 223 if (!clear && cache.exists()) { 224 try { 225 cpath.restore(cache); 226 } catch (IOException e) { // rebuild cache 227 cpath.scan(classPath, scanForNestedJARs, libdirs); 228 cpath.store(cache); 229 } 230 } else { 231 cpath.scan(classPath, scanForNestedJARs, libdirs); 232 cpath.store(cache); 233 } 234 installAll(cpath.getBundles()); 235 // new ApplicationBundleLoader(this, !clear).loadBundles(classPath); 236 } 237 238 public void install(BundleFile bf) throws BundleException { 239 install(new BundleImpl(this, bf, classLoader.getLoader())); 240 } 241 242 public void installAll(List<BundleFile> bundles) throws BundleException { 243 for (BundleFile bf : bundles) { 244 install(new BundleImpl(this, bf, classLoader.getLoader())); 245 } 246 } 247 248 /** 249 * Creates the system bundle from the jar specified by the nuxeo.osgi.system.bundle property. 250 */ 251 public static BundleFile createSystemBundle(URL systemBundle) throws IOException { 252 URI uri; 253 try { 254 uri = systemBundle.toURI(); 255 } catch (URISyntaxException e) { 256 throw new IOException(e); 257 } 258 File file = new File(uri); 259 BundleFile sysbf = null; 260 if (file.isFile()) { 261 sysbf = new JarBundleFile(file); 262 } else { 263 sysbf = new DirectoryBundleFile(file); 264 } 265 return sysbf; 266 } 267 268 public static CommandLineOptions getComandLineOptions() { 269 return options; 270 } 271 272 public static boolean hasCommandLineOption(String option) { 273 return options != null && options.hasOption(option); 274 } 275 276 public static Environment createEnvironment() throws IOException { 277 if (options != null) { 278 String val = options.getOption("home"); 279 if (val == null) { 280 val = "."; 281 } 282 File home = new File(val); 283 home = home.getCanonicalFile(); 284 Environment env = new Environment(home); 285 env.setCommandLineArguments(args); 286 val = options.getOption("data"); 287 if (val != null) { 288 env.setData(new File(val).getCanonicalFile()); 289 } 290 val = options.getOption("log"); 291 if (val != null) { 292 env.setLog(new File(val).getCanonicalFile()); 293 } 294 val = options.getOption("config"); 295 if (val != null) { 296 env.setConfig(new File(val).getCanonicalFile()); 297 } 298 val = options.getOption("web"); 299 if (val != null) { 300 env.setWeb(new File(val).getCanonicalFile()); 301 } 302 val = options.getOption("tmp"); 303 if (val != null) { 304 env.setTemp(new File(val).getCanonicalFile()); 305 } 306 val = options.getOption("bundles"); 307 if (val != null) { 308 env.setPath(Environment.BUNDLES, new File(val).getCanonicalFile()); 309 } 310 env.setHostApplicationName(Environment.NXSERVER_HOST); 311 env.setHostApplicationVersion("1.0.0"); 312 env.getData().mkdirs(); 313 env.getLog().mkdirs(); 314 env.getTemp().mkdirs(); 315 return env; 316 } else { 317 return new Environment(new File("").getCanonicalFile()); 318 } 319 } 320 321 public static void setMainTask(Runnable mainTask) { 322 StandaloneApplication.mainTask = mainTask; 323 } 324 325 public static Runnable getMainTask() { 326 return mainTask; 327 } 328 329 public static void main(URL systemBundle, List<File> classPath, String[] args) throws Exception { 330 SharedClassLoader classLoader = (SharedClassLoader) Thread.currentThread().getContextClassLoader(); 331 long startTime = System.currentTimeMillis(); 332 // parse command line args 333 StandaloneApplication.args = args; 334 options = new CommandLineOptions(args); 335 // start framework 336 StandaloneApplication app = createInstance(classLoader); 337 // start level 0 338 app.setClassPath(classPath); 339 app.setSystemBundle(new SystemBundle(app, createSystemBundle(systemBundle), classLoader.getLoader())); 340 // start level 1 341 app.start(); 342 log.info("Framework started in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec."); 343 if (mainTask != null) { 344 mainTask.run(); 345 } 346 } 347 348}