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 */
019package org.nuxeo.osgi.application.loader;
020
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029import java.util.Map;
030import java.util.Properties;
031import java.util.jar.Attributes;
032import java.util.jar.JarFile;
033import java.util.jar.Manifest;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.common.Environment;
038import org.nuxeo.common.utils.StringUtils;
039import org.nuxeo.osgi.BundleFile;
040import org.nuxeo.osgi.BundleImpl;
041import org.nuxeo.osgi.DirectoryBundleFile;
042import org.nuxeo.osgi.JarBundleFile;
043import org.nuxeo.osgi.OSGiAdapter;
044import org.nuxeo.osgi.SystemBundle;
045import org.nuxeo.osgi.SystemBundleFile;
046import org.osgi.framework.BundleException;
047import org.osgi.framework.Constants;
048import org.osgi.framework.FrameworkEvent;
049
050/**
051 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
052 */
053public class FrameworkLoader {
054
055    public static final String HOST_NAME = "org.nuxeo.app.host.name";
056
057    public static final String HOST_VERSION = "org.nuxeo.app.host.version";
058
059    /**
060     * @deprecated prefer use of {@link Environment#NUXEO_TMP_DIR}
061     */
062    @Deprecated
063    public static final String TMP_DIR = "org.nuxeo.app.tmp";
064
065    public static final String LIBS = "org.nuxeo.app.libs"; // class path
066
067    public static final String BUNDLES = "org.nuxeo.app.bundles"; // class path
068
069    public static final String DEVMODE = "org.nuxeo.app.devmode";
070
071    public static final String PREPROCESSING = "org.nuxeo.app.preprocessing";
072
073    public static final String SCAN_FOR_NESTED_JARS = "org.nuxeo.app.scanForNestedJars";
074
075    public static final String FLUSH_CACHE = "org.nuxeo.app.flushCache";
076
077    public static final String ARGS = "org.nuxeo.app.args";
078
079    private static final Log log = LogFactory.getLog(FrameworkLoader.class);
080
081    private static boolean isInitialized;
082
083    private static boolean isStarted;
084
085    private static File home;
086
087    private static ClassLoader loader;
088
089    private static List<File> bundleFiles;
090
091    private static OSGiAdapter osgi;
092
093    public static OSGiAdapter osgi() {
094        return osgi;
095    }
096
097    public static ClassLoader getLoader() {
098        return loader;
099    }
100
101    public static synchronized void initialize(ClassLoader cl, File home, List<File> bundleFiles,
102            Map<String, Object> hostEnv) {
103        if (isInitialized) {
104            return;
105        }
106        FrameworkLoader.home = home;
107        FrameworkLoader.bundleFiles = bundleFiles == null ? new ArrayList<File>() : bundleFiles;
108        Collections.sort(FrameworkLoader.bundleFiles);
109
110        loader = cl;
111        doInitialize(hostEnv);
112        osgi = new OSGiAdapter(home);
113        isInitialized = true;
114    }
115
116    public static synchronized void start() throws BundleException {
117        if (isStarted) {
118            return;
119        }
120        if (!isInitialized) {
121            throw new IllegalStateException("Framework is not initialized. Call initialize method first");
122        }
123        ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
124        try {
125            Thread.currentThread().setContextClassLoader(loader);
126            doStart();
127        } finally {
128            Thread.currentThread().setContextClassLoader(oldCl);
129        }
130        isStarted = true;
131    }
132
133    public static synchronized void stop() throws BundleException {
134        if (!isStarted) {
135            return;
136        }
137        ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
138        try {
139            Thread.currentThread().setContextClassLoader(loader);
140            doStop();
141        } finally {
142            Thread.currentThread().setContextClassLoader(oldCl);
143        }
144        isStarted = false;
145    }
146
147    private static void doInitialize(Map<String, Object> hostEnv) {
148        // make sure this property was correctly initialized
149        System.setProperty(Environment.HOME_DIR, home.getAbsolutePath());
150        boolean doPreprocessing = true;
151        String v = (String) hostEnv.get(PREPROCESSING);
152        if (v != null) {
153            doPreprocessing = Boolean.parseBoolean(v);
154        }
155        // build environment
156        Environment env = createEnvironment(home, hostEnv);
157        Environment.setDefault(env);
158        loadSystemProperties();
159        // start bundle pre-processing if requested
160        if (doPreprocessing) {
161            try {
162                preprocess();
163            } catch (RuntimeException e) {
164                throw new RuntimeException("Failed to run preprocessing", e);
165            }
166        }
167    }
168
169    protected static void printDeploymentOrderInfo(List<File> files) {
170        if (log.isDebugEnabled()) {
171            StringBuilder buf = new StringBuilder();
172            for (File file : files) {
173                if (file != null) {
174                    buf.append("\n\t" + file.getPath());
175                }
176            }
177            log.debug("Deployment order: " + buf.toString());
178        }
179    }
180
181    protected static Attributes.Name SYMBOLIC_NAME = new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME);
182
183    protected static boolean isBundle(File f) {
184        Manifest mf;
185        try {
186            if (f.isFile()) { // jar file
187                JarFile jf = new JarFile(f);
188                try {
189                    mf = jf.getManifest();
190                } finally {
191                    jf.close();
192                }
193                if (mf == null) {
194                    return false;
195                }
196            } else if (f.isDirectory()) { // directory
197                f = new File(f, "META-INF/MANIFEST.MF");
198                if (!f.isFile()) {
199                    return false;
200                }
201                mf = new Manifest();
202                FileInputStream input = new FileInputStream(f);
203                try {
204                    mf.read(input);
205                } finally {
206                    input.close();
207                }
208            } else {
209                return false;
210            }
211        } catch (IOException e) {
212            return false;
213        }
214        return mf.getMainAttributes().containsKey(SYMBOLIC_NAME);
215    }
216
217    private static void doStart() throws BundleException {
218        printStartMessage();
219        // install system bundle first
220        BundleFile bf;
221        try {
222            bf = new SystemBundleFile(home);
223        } catch (IOException e) {
224            throw new BundleException("Cannot create system bundle for " + home, e);
225        }
226        SystemBundle systemBundle = new SystemBundle(osgi, bf, loader);
227        osgi.setSystemBundle(systemBundle);
228        printDeploymentOrderInfo(bundleFiles);
229        for (File f : bundleFiles) {
230            if (!isBundle(f)) {
231                continue;
232            }
233            try {
234                install(f);
235            } catch (IOException e) {
236                log.error("Failed to install bundle: " + f, e);
237                // continue
238            } catch (BundleException e) {
239                log.error("Failed to install bundle: " + f, e);
240                // continue
241            } catch (RuntimeException e) {
242                log.error("Failed to install bundle: " + f, e);
243                // continue
244            }
245        }
246        osgi.fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, systemBundle, null));
247        // osgi.fireFrameworkEvent(new
248        // FrameworkEvent(FrameworkEvent.AFTER_START, systemBundle, null));
249    }
250
251    private static void doStop() throws BundleException {
252        try {
253            osgi.shutdown();
254        } catch (IOException e) {
255            throw new BundleException("Cannot shutdown OSGi", e);
256        }
257    }
258
259    public static void uninstall(String symbolicName) throws BundleException {
260        BundleImpl bundle = osgi.getBundle(symbolicName);
261        if (bundle != null) {
262            bundle.uninstall();
263        }
264    }
265
266    public static String install(File f) throws IOException, BundleException {
267        BundleFile bf = null;
268        if (f.isDirectory()) {
269            bf = new DirectoryBundleFile(f);
270        } else {
271            bf = new JarBundleFile(f);
272        }
273        BundleImpl bundle = new BundleImpl(osgi, bf, loader);
274        if (bundle.getState() == 0) {
275            // not a bundle (no Bundle-SymbolicName)
276            return null;
277        }
278        osgi.install(bundle);
279        return bundle.getSymbolicName();
280    }
281
282    public static void preprocess() {
283        File f = new File(home, "OSGI-INF/deployment-container.xml");
284        if (!f.isFile()) { // make sure a preprocessing container is defined
285            return;
286        }
287        try {
288            Class<?> klass = loader.loadClass("org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor");
289            Method main = klass.getMethod("main", String[].class);
290            main.invoke(null, new Object[] { new String[] { home.getAbsolutePath() } });
291        } catch (ClassNotFoundException e) {
292            throw new RuntimeException(e);
293        } catch (SecurityException e) {
294            throw new RuntimeException(e);
295        } catch (NoSuchMethodException e) {
296            throw new RuntimeException(e);
297        } catch (IllegalAccessException e) {
298            throw new RuntimeException(e);
299        } catch (InvocationTargetException e) {
300            throw new RuntimeException(e);
301        }
302    }
303
304    protected static void loadSystemProperties() {
305        System.setProperty(Environment.HOME_DIR, home.getAbsolutePath());
306        File file = new File(home, "system.properties");
307        if (!file.isFile()) {
308            return;
309        }
310        FileInputStream in = null;
311        try {
312            in = new FileInputStream(file);
313            Properties p = new Properties();
314            p.load(in);
315            for (Map.Entry<Object, Object> entry : p.entrySet()) {
316                String v = (String) entry.getValue();
317                v = StringUtils.expandVars(v, System.getProperties());
318                System.setProperty((String) entry.getKey(), v);
319            }
320        } catch (IOException e) {
321            throw new RuntimeException("Failed to load system properties", e);
322        } finally {
323            if (in != null) {
324                try {
325                    in.close();
326                } catch (IOException e) {
327                    log.error(e);
328                }
329            }
330        }
331    }
332
333    protected static String getEnvProperty(String key, Map<String, Object> hostEnv, Properties sysprops,
334            boolean addToSystemProperties) {
335        String v = (String) hostEnv.get(key);
336        if (v == null) {
337            v = System.getProperty(key);
338        }
339        if (v != null) {
340            v = StringUtils.expandVars(v, sysprops);
341            if (addToSystemProperties) {
342                sysprops.setProperty(key, v);
343            }
344        }
345        return v;
346    }
347
348    protected static Environment createEnvironment(File home, Map<String, Object> hostEnv) {
349        Properties sysprops = System.getProperties();
350        sysprops.setProperty(Environment.NUXEO_RUNTIME_HOME, home.getAbsolutePath());
351
352        Environment env = new Environment(home);
353        String v = (String) hostEnv.get(HOST_NAME);
354        env.setHostApplicationName(v == null ? Environment.NXSERVER_HOST : v);
355        v = (String) hostEnv.get(HOST_VERSION);
356        if (v != null) {
357            env.setHostApplicationVersion((String) hostEnv.get(HOST_VERSION));
358        }
359
360        v = getEnvProperty(Environment.NUXEO_DATA_DIR, hostEnv, sysprops, true);
361        if (v != null) {
362            env.setData(new File(v));
363        } else {
364            sysprops.setProperty(Environment.NUXEO_DATA_DIR, env.getData().getAbsolutePath());
365        }
366
367        v = getEnvProperty(Environment.NUXEO_LOG_DIR, hostEnv, sysprops, true);
368        if (v != null) {
369            env.setLog(new File(v));
370        } else {
371            sysprops.setProperty(Environment.NUXEO_LOG_DIR, env.getLog().getAbsolutePath());
372        }
373
374        v = getEnvProperty(Environment.NUXEO_TMP_DIR, hostEnv, sysprops, true);
375        if (v != null) {
376            env.setTemp(new File(v));
377        } else {
378            sysprops.setProperty(Environment.NUXEO_TMP_DIR, env.getTemp().getAbsolutePath());
379        }
380
381        v = getEnvProperty(Environment.NUXEO_CONFIG_DIR, hostEnv, sysprops, true);
382        if (v != null) {
383            env.setConfig(new File(v));
384        } else {
385            sysprops.setProperty(Environment.NUXEO_CONFIG_DIR, env.getConfig().getAbsolutePath());
386        }
387
388        v = getEnvProperty(Environment.NUXEO_WEB_DIR, hostEnv, sysprops, true);
389        if (v != null) {
390            env.setWeb(new File(v));
391        } else {
392            sysprops.setProperty(Environment.NUXEO_WEB_DIR, env.getWeb().getAbsolutePath());
393        }
394
395        v = (String) hostEnv.get(ARGS);
396        if (v != null) {
397            env.setCommandLineArguments(v.split("\\s+"));
398        } else {
399            env.setCommandLineArguments(new String[0]);
400        }
401        env.getData().mkdirs();
402        env.getLog().mkdirs();
403        env.getTemp().mkdirs();
404
405        return env;
406    }
407
408    protected static void printStartMessage() {
409        StringBuilder msg = getStartMessage();
410        log.info(msg);
411    }
412
413    /**
414     * @since 5.5
415     * @return Environment summary
416     */
417    protected static StringBuilder getStartMessage() {
418        String newline = System.getProperty("line.separator");
419        Environment env = Environment.getDefault();
420        String hr = "======================================================================";
421        StringBuilder msg = new StringBuilder(newline);
422        msg.append(hr + newline);
423        msg.append("= Starting Nuxeo Framework" + newline);
424        msg.append(hr + newline);
425        msg.append("  * Server home = " + env.getServerHome() + newline);
426        msg.append("  * Runtime home = " + env.getRuntimeHome() + newline);
427        msg.append("  * Data Directory = " + env.getData() + newline);
428        msg.append("  * Log Directory = " + env.getLog() + newline);
429        msg.append("  * Configuration Directory = " + env.getConfig() + newline);
430        msg.append("  * Temp Directory = " + env.getTemp() + newline);
431        // System.out.println("  * System Bundle = "+systemBundle);
432        // System.out.println("  * Command Line Args = "+Arrays.asList(env.getCommandLineArguments()));
433        msg.append(hr);
434        return msg;
435    }
436}