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}