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 */
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<>() : 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
124        try {
125            doStart();
126        } finally {
127            isStarted = true;
128        }
129    }
130
131    public static synchronized void stop() throws BundleException {
132        if (!isStarted) {
133            return;
134        }
135        try {
136            doStop();
137        } finally {
138            isStarted = false;
139        }
140    }
141
142    private static void doInitialize(Map<String, Object> hostEnv) {
143        // make sure this property was correctly initialized
144        System.setProperty(Environment.HOME_DIR, home.getAbsolutePath());
145        boolean doPreprocessing = true;
146        String v = (String) hostEnv.get(PREPROCESSING);
147        if (v != null) {
148            doPreprocessing = Boolean.parseBoolean(v);
149        }
150        // build environment
151        Environment env = createEnvironment(home, hostEnv);
152        Environment.setDefault(env);
153        loadSystemProperties();
154        // start bundle pre-processing if requested
155        if (doPreprocessing) {
156            try {
157                preprocess();
158            } catch (RuntimeException e) {
159                throw new RuntimeException("Failed to run preprocessing", e);
160            }
161        }
162    }
163
164    protected static void printDeploymentOrderInfo(List<File> files) {
165        if (log.isDebugEnabled()) {
166            StringBuilder buf = new StringBuilder();
167            for (File file : files) {
168                if (file != null) {
169                    buf.append("\n\t" + file.getPath());
170                }
171            }
172            log.debug("Deployment order: " + buf.toString());
173        }
174    }
175
176    protected static Attributes.Name SYMBOLIC_NAME = new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME);
177
178    protected static boolean isBundle(File f) {
179        Manifest mf;
180        try {
181            if (f.isFile()) { // jar file
182                JarFile jf = new JarFile(f);
183                try {
184                    mf = jf.getManifest();
185                } finally {
186                    jf.close();
187                }
188                if (mf == null) {
189                    return false;
190                }
191            } else if (f.isDirectory()) { // directory
192                f = new File(f, "META-INF/MANIFEST.MF");
193                if (!f.isFile()) {
194                    return false;
195                }
196                mf = new Manifest();
197                FileInputStream input = new FileInputStream(f);
198                try {
199                    mf.read(input);
200                } finally {
201                    input.close();
202                }
203            } else {
204                return false;
205            }
206        } catch (IOException e) {
207            return false;
208        }
209        return mf.getMainAttributes().containsKey(SYMBOLIC_NAME);
210    }
211
212    private static void doStart() throws BundleException {
213        printStartMessage();
214        // install system bundle first
215        BundleFile bf;
216        try {
217            bf = new SystemBundleFile(home);
218        } catch (IOException e) {
219            throw new BundleException("Cannot create system bundle for " + home, e);
220        }
221        SystemBundle systemBundle = new SystemBundle(osgi, bf, loader);
222        osgi.setSystemBundle(systemBundle);
223        printDeploymentOrderInfo(bundleFiles);
224        for (File f : bundleFiles) {
225            if (!isBundle(f)) {
226                continue;
227            }
228            try {
229                install(f);
230            } catch (IOException e) {
231                log.error("Failed to install bundle: " + f, e);
232                // continue
233            } catch (BundleException e) {
234                log.error("Failed to install bundle: " + f, e);
235                // continue
236            } catch (RuntimeException e) {
237                log.error("Failed to install bundle: " + f, e);
238                // continue
239            }
240        }
241        osgi.fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, systemBundle, null));
242        // osgi.fireFrameworkEvent(new
243        // FrameworkEvent(FrameworkEvent.AFTER_START, systemBundle, null));
244    }
245
246    private static void doStop() throws BundleException {
247        try {
248            osgi.shutdown();
249        } catch (IOException e) {
250            throw new BundleException("Cannot shutdown OSGi", e);
251        }
252    }
253
254    public static void uninstall(String symbolicName) throws BundleException {
255        BundleImpl bundle = osgi.getBundle(symbolicName);
256        if (bundle != null) {
257            bundle.uninstall();
258        }
259    }
260
261    public static String install(File f) throws IOException, BundleException {
262        BundleFile bf = null;
263        if (f.isDirectory()) {
264            bf = new DirectoryBundleFile(f);
265        } else {
266            bf = new JarBundleFile(f);
267        }
268        BundleImpl bundle = new BundleImpl(osgi, bf, loader);
269        if (bundle.getState() == 0) {
270            // not a bundle (no Bundle-SymbolicName)
271            return null;
272        }
273        osgi.install(bundle);
274        return bundle.getSymbolicName();
275    }
276
277    public static void preprocess() {
278        File f = new File(home, "OSGI-INF/deployment-container.xml");
279        if (!f.isFile()) { // make sure a preprocessing container is defined
280            return;
281        }
282        try {
283            Class<?> klass = loader.loadClass("org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor");
284            Method main = klass.getMethod("main", String[].class);
285            main.invoke(null, new Object[] { new String[] { home.getAbsolutePath() } });
286        } catch (ClassNotFoundException e) {
287            throw new RuntimeException(e);
288        } catch (SecurityException e) {
289            throw new RuntimeException(e);
290        } catch (NoSuchMethodException e) {
291            throw new RuntimeException(e);
292        } catch (IllegalAccessException e) {
293            throw new RuntimeException(e);
294        } catch (InvocationTargetException e) {
295            throw new RuntimeException(e);
296        }
297    }
298
299    protected static void loadSystemProperties() {
300        System.setProperty(Environment.HOME_DIR, home.getAbsolutePath());
301        File file = new File(home, "system.properties");
302        if (!file.isFile()) {
303            return;
304        }
305        FileInputStream in = null;
306        try {
307            in = new FileInputStream(file);
308            Properties p = new Properties();
309            p.load(in);
310            for (Map.Entry<Object, Object> entry : p.entrySet()) {
311                String v = (String) entry.getValue();
312                v = StringUtils.expandVars(v, System.getProperties());
313                System.setProperty((String) entry.getKey(), v);
314            }
315        } catch (IOException e) {
316            throw new RuntimeException("Failed to load system properties", e);
317        } finally {
318            if (in != null) {
319                try {
320                    in.close();
321                } catch (IOException e) {
322                    log.error(e);
323                }
324            }
325        }
326    }
327
328    protected static String getEnvProperty(String key, Map<String, Object> hostEnv, Properties sysprops,
329            boolean addToSystemProperties) {
330        String v = (String) hostEnv.get(key);
331        if (v == null) {
332            v = System.getProperty(key);
333        }
334        if (v != null) {
335            v = StringUtils.expandVars(v, sysprops);
336            if (addToSystemProperties) {
337                sysprops.setProperty(key, v);
338            }
339        }
340        return v;
341    }
342
343    protected static Environment createEnvironment(File home, Map<String, Object> hostEnv) {
344        Properties sysprops = System.getProperties();
345        sysprops.setProperty(Environment.NUXEO_RUNTIME_HOME, home.getAbsolutePath());
346
347        Environment env = Environment.getDefault();
348        if (env == null) {
349            env = new Environment(home);
350        }
351        if (!home.equals(env.getRuntimeHome())) {
352            env.setRuntimeHome(home);
353        }
354
355        String v = (String) hostEnv.get(HOST_NAME);
356        env.setHostApplicationName(v == null ? Environment.NXSERVER_HOST : v);
357        v = (String) hostEnv.get(HOST_VERSION);
358        if (v != null) {
359            env.setHostApplicationVersion((String) hostEnv.get(HOST_VERSION));
360        }
361
362        v = getEnvProperty(Environment.NUXEO_DATA_DIR, hostEnv, sysprops, true);
363        if (v != null) {
364            env.setData(new File(v));
365        } else {
366            sysprops.setProperty(Environment.NUXEO_DATA_DIR, env.getData().getAbsolutePath());
367        }
368
369        v = getEnvProperty(Environment.NUXEO_LOG_DIR, hostEnv, sysprops, true);
370        if (v != null) {
371            env.setLog(new File(v));
372        } else {
373            sysprops.setProperty(Environment.NUXEO_LOG_DIR, env.getLog().getAbsolutePath());
374        }
375
376        v = getEnvProperty(Environment.NUXEO_TMP_DIR, hostEnv, sysprops, true);
377        if (v != null) {
378            env.setTemp(new File(v));
379        } else {
380            sysprops.setProperty(Environment.NUXEO_TMP_DIR, env.getTemp().getAbsolutePath());
381        }
382
383        v = getEnvProperty(Environment.NUXEO_CONFIG_DIR, hostEnv, sysprops, true);
384        if (v != null) {
385            env.setConfig(new File(v));
386        } else {
387            sysprops.setProperty(Environment.NUXEO_CONFIG_DIR, env.getConfig().getAbsolutePath());
388        }
389
390        v = getEnvProperty(Environment.NUXEO_WEB_DIR, hostEnv, sysprops, true);
391        if (v != null) {
392            env.setWeb(new File(v));
393        } else {
394            sysprops.setProperty(Environment.NUXEO_WEB_DIR, env.getWeb().getAbsolutePath());
395        }
396
397        v = (String) hostEnv.get(ARGS);
398        if (v != null) {
399            env.setCommandLineArguments(v.split("\\s+"));
400        } else {
401            env.setCommandLineArguments(new String[0]);
402        }
403        return env;
404    }
405
406    protected static void printStartMessage() {
407        StringBuilder msg = getStartMessage();
408        log.info(msg);
409    }
410
411    /**
412     * @since 5.5
413     * @return Environment summary
414     */
415    protected static StringBuilder getStartMessage() {
416        String newline = System.getProperty("line.separator");
417        Environment env = Environment.getDefault();
418        String hr = "======================================================================";
419        StringBuilder msg = new StringBuilder(newline);
420        msg.append(hr + newline);
421        msg.append("= Starting Nuxeo Framework" + newline);
422        msg.append(hr + newline);
423        msg.append("  * Server home = " + env.getServerHome() + newline);
424        msg.append("  * Runtime home = " + env.getRuntimeHome() + newline);
425        msg.append("  * Data Directory = " + env.getData() + newline);
426        msg.append("  * Log Directory = " + env.getLog() + newline);
427        msg.append("  * Configuration Directory = " + env.getConfig() + newline);
428        msg.append("  * Temp Directory = " + env.getTemp() + newline);
429        // System.out.println("  * System Bundle = "+systemBundle);
430        // System.out.println("  * Command Line Args = "+Arrays.asList(env.getCommandLineArguments()));
431        msg.append(hr);
432        return msg;
433    }
434}