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