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