/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu, jcarsique
*
*/
package org.nuxeo.osgi.application;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.Environment;
import org.nuxeo.common.utils.StringUtils;
import org.nuxeo.osgi.BundleFile;
import org.nuxeo.osgi.BundleImpl;
import org.nuxeo.osgi.DirectoryBundleFile;
import org.nuxeo.osgi.JarBundleFile;
import org.nuxeo.osgi.OSGiAdapter;
import org.nuxeo.osgi.SystemBundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class StandaloneApplication extends OSGiAdapter {
public static final String MAIN_TASK = "org.nuxeo.osgi.application.main.task";
private static final Log log = LogFactory.getLog(StandaloneApplication.class);
private static StandaloneApplication instance;
private static CommandLineOptions options; // TODO should be remove
private static String[] args;
private static Runnable mainTask;
protected final SharedClassLoader classLoader;
protected final Environment env;
protected boolean isStarted;
protected File home;
protected List<File> classPath;
protected boolean scanForNestedJARs = true; // by default true
// a list of class path prefixes that contains JARS that should not be
// treated as bundles.
protected String[] libdirs;
public static StandaloneApplication getInstance() {
return instance;
}
public static StandaloneApplication createInstance(SharedClassLoader cl) throws IOException {
if (instance != null) {
throw new IllegalStateException("Application already instantiated");
}
// create application environment
Environment env = createEnvironment();
Environment.setDefault(env);
instance = new StandaloneApplication(cl, env);
String val = options.getOption("scanForNestedJARs");
if (val != null) {
StandaloneApplication.instance.scanForNestedJARs = Boolean.parseBoolean(val);
}
// hack to avoid deploying all jars in classpath as bundles
String javaLibsProp = System.getProperty("org.nuxeo.launcher.libdirs");
if (javaLibsProp != null) {
String[] ar = StringUtils.split(javaLibsProp, ',', false);
if (ar.length > 0) {
instance.libdirs = ar;
File wd = instance.getWorkingDir();
for (int i = 0; i < ar.length; i++) {
if (!ar[i].startsWith("/")) {
instance.libdirs[i] = new File(wd, ar[i]).getCanonicalFile().getAbsolutePath();
}
}
}
}
// end hack
return instance;
}
private StandaloneApplication(SharedClassLoader cl, Environment env) {
super(env.getHome(), env.getData(), env.getProperties());
classLoader = cl;
this.env = env;
}
public SharedClassLoader getSharedClassLoader() {
return classLoader;
}
public Environment getEnvironment() {
return env;
}
public void start() throws IOException, BundleException {
if (isStarted) {
throw new IllegalStateException("OSGi Application is already started");
}
List<BundleFile> preBundles = loadUserBundles("pre-bundles");
List<BundleFile> postBundles = loadUserBundles("post-bundles");
// start level 1
// start bundles that are specified in the osgi.bundles property
if (preBundles != null) {
startBundles(preBundles);
}
// start level 2
// if needed install all discovered bundles (the one that are located in
// bundles dir)
autoInstallBundles();
// start level 3
if (postBundles != null) {
startBundles(postBundles);
}
fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, getSystemBundle(), null));
isStarted = true;
}
public boolean isStarted() {
return isStarted;
}
@Override
public void shutdown() throws IOException {
if (!isStarted) {
throw new IllegalStateException("OSGi Application was not started");
}
try {
super.shutdown();
} finally {
isStarted = false;
}
}
protected void startBundles(List<BundleFile> bundles) throws BundleException {
for (BundleFile bf : bundles) {
this.install(new BundleImpl(this, bf, classLoader.getLoader()));
}
}
protected List<BundleFile> loadUserBundles(String key) throws IOException {
if (options == null) {
return null;
}
String bundlesString = options.getOption(key);
if (bundlesString == null) {
return null; // no bundles to load
}
List<BundleFile> bundles = new ArrayList<>();
String[] ar = StringUtils.split(bundlesString, ':', true);
for (String entry : ar) {
File file;
if (entry.contains("file:")) {
try {
URL url = new URL(entry);
file = new File(url.toURI());
} catch (MalformedURLException e) {
throw new IOException(e);
} catch (URISyntaxException e) {
throw new IOException(e);
}
} else {
file = new File(entry);
}
BundleFile bf;
if (file.isDirectory()) {
bf = new DirectoryBundleFile(file);
} else {
bf = new JarBundleFile(file);
}
classLoader.addURL(bf.getURL());
bundles.add(bf);
}
return bundles;
}
public List<File> getClassPath() {
return classPath;
}
public void setClassPath(List<File> classPath) {
this.classPath = classPath;
}
protected void autoInstallBundles() throws IOException, BundleException {
List<File> cp = getClassPath();
if (cp == null || cp.isEmpty()) {
return;
}
boolean clear = hasCommandLineOption("clear");
ClassPath cpath = new ClassPath(classLoader, new File(env.getData(), "nested-jars"));
File cache = new File(env.getData(), "bundles.cache");
if (!clear && cache.exists()) {
try {
cpath.restore(cache);
} catch (IOException e) { // rebuild cache
cpath.scan(classPath, scanForNestedJARs, libdirs);
cpath.store(cache);
}
} else {
cpath.scan(classPath, scanForNestedJARs, libdirs);
cpath.store(cache);
}
installAll(cpath.getBundles());
// new ApplicationBundleLoader(this, !clear).loadBundles(classPath);
}
public void install(BundleFile bf) throws BundleException {
install(new BundleImpl(this, bf, classLoader.getLoader()));
}
public void installAll(List<BundleFile> bundles) throws BundleException {
for (BundleFile bf : bundles) {
install(new BundleImpl(this, bf, classLoader.getLoader()));
}
}
/**
* Creates the system bundle from the jar specified by the nuxeo.osgi.system.bundle property.
*/
public static BundleFile createSystemBundle(URL systemBundle) throws IOException {
URI uri;
try {
uri = systemBundle.toURI();
} catch (URISyntaxException e) {
throw new IOException(e);
}
File file = new File(uri);
BundleFile sysbf = null;
if (file.isFile()) {
sysbf = new JarBundleFile(file);
} else {
sysbf = new DirectoryBundleFile(file);
}
return sysbf;
}
public static CommandLineOptions getComandLineOptions() {
return options;
}
public static boolean hasCommandLineOption(String option) {
return options != null && options.hasOption(option);
}
public static Environment createEnvironment() throws IOException {
if (options != null) {
String val = options.getOption("home");
if (val == null) {
val = ".";
}
File home = new File(val);
home = home.getCanonicalFile();
Environment env = new Environment(home);
env.setCommandLineArguments(args);
val = options.getOption("data");
if (val != null) {
env.setData(new File(val).getCanonicalFile());
}
val = options.getOption("log");
if (val != null) {
env.setLog(new File(val).getCanonicalFile());
}
val = options.getOption("config");
if (val != null) {
env.setConfig(new File(val).getCanonicalFile());
}
val = options.getOption("web");
if (val != null) {
env.setWeb(new File(val).getCanonicalFile());
}
val = options.getOption("tmp");
if (val != null) {
env.setTemp(new File(val).getCanonicalFile());
}
val = options.getOption("bundles");
if (val != null) {
env.setPath(Environment.BUNDLES, new File(val).getCanonicalFile());
}
env.setHostApplicationName(Environment.NXSERVER_HOST);
env.setHostApplicationVersion("1.0.0");
env.getData().mkdirs();
env.getLog().mkdirs();
env.getTemp().mkdirs();
return env;
} else {
return new Environment(new File("").getCanonicalFile());
}
}
public static void setMainTask(Runnable mainTask) {
StandaloneApplication.mainTask = mainTask;
}
public static Runnable getMainTask() {
return mainTask;
}
public static void main(URL systemBundle, List<File> classPath, String[] args) throws Exception {
SharedClassLoader classLoader = (SharedClassLoader) Thread.currentThread().getContextClassLoader();
long startTime = System.currentTimeMillis();
// parse command line args
StandaloneApplication.args = args;
options = new CommandLineOptions(args);
// start framework
StandaloneApplication app = createInstance(classLoader);
// start level 0
app.setClassPath(classPath);
app.setSystemBundle(new SystemBundle(app, createSystemBundle(systemBundle), classLoader.getLoader()));
// start level 1
app.start();
log.info("Framework started in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec.");
if (mainTask != null) {
mainTask.run();
}
}
}