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