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 buf = new StringBuilder();
164            for (File file : files) {
165                if (file != null) {
166                    buf.append("\n\t").append(file.getPath());
167                }
168            }
169            log.debug("Deployment order: " + buf.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        // osgi.fireFrameworkEvent(new
227        // FrameworkEvent(FrameworkEvent.AFTER_START, systemBundle, null));
228    }
229
230    private static void doStop() throws BundleException {
231        try {
232            osgi.shutdown();
233        } catch (IOException e) {
234            throw new BundleException("Cannot shutdown OSGi", e);
235        }
236    }
237
238    public static void uninstall(String symbolicName) throws BundleException {
239        BundleImpl bundle = osgi.getBundle(symbolicName);
240        if (bundle != null) {
241            bundle.uninstall();
242        }
243    }
244
245    public static String install(File f) throws IOException, BundleException {
246        BundleFile bf;
247        if (f.isDirectory()) {
248            bf = new DirectoryBundleFile(f);
249        } else {
250            bf = new JarBundleFile(f);
251        }
252        BundleImpl bundle = new BundleImpl(osgi, bf, loader);
253        if (bundle.getState() == 0) {
254            // not a bundle (no Bundle-SymbolicName)
255            return null;
256        }
257        osgi.install(bundle);
258        return bundle.getSymbolicName();
259    }
260
261    public static void preprocess() {
262        File f = new File(home, "OSGI-INF/deployment-container.xml");
263        if (!f.isFile()) { // make sure a preprocessing container is defined
264            return;
265        }
266        try {
267            Class<?> klass = loader.loadClass("org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor");
268            Method main = klass.getMethod("main", String[].class);
269            main.invoke(null, new Object[] { new String[] { home.getAbsolutePath() } });
270        } catch (ReflectiveOperationException e) {
271            throw new RuntimeException(e);
272        }
273    }
274
275    protected static void loadSystemProperties() {
276        File file = new File(home, "system.properties");
277        if (!file.isFile()) {
278            return;
279        }
280        FileInputStream in = null;
281        try {
282            in = new FileInputStream(file);
283            Properties p = new Properties();
284            p.load(in);
285            for (Map.Entry<Object, Object> entry : p.entrySet()) {
286                String v = (String) entry.getValue();
287                v = StringUtils.expandVars(v, System.getProperties());
288                System.setProperty((String) entry.getKey(), v);
289            }
290        } catch (IOException e) {
291            throw new RuntimeException("Failed to load system properties", e);
292        } finally {
293            if (in != null) {
294                try {
295                    in.close();
296                } catch (IOException e) {
297                    log.error(e);
298                }
299            }
300        }
301    }
302
303    protected static String getEnvProperty(String key, Map<String, Object> hostEnv, Properties sysprops,
304            boolean addToSystemProperties) {
305        String v = (String) hostEnv.get(key);
306        if (v == null) {
307            v = System.getProperty(key);
308        }
309        if (v != null) {
310            v = StringUtils.expandVars(v, sysprops);
311            if (addToSystemProperties) {
312                sysprops.setProperty(key, v);
313            }
314        }
315        return v;
316    }
317
318    protected static Environment createEnvironment(File home, Map<String, Object> hostEnv) {
319        Properties sysprops = System.getProperties();
320        sysprops.setProperty(Environment.NUXEO_RUNTIME_HOME, home.getAbsolutePath());
321
322        Environment env = Environment.getDefault();
323        if (env == null) {
324            env = new Environment(home);
325        }
326        if (!home.equals(env.getRuntimeHome())) {
327            env.setRuntimeHome(home);
328        }
329
330        String v = (String) hostEnv.get(HOST_NAME);
331        env.setHostApplicationName(v == null ? Environment.NXSERVER_HOST : v);
332        v = (String) hostEnv.get(HOST_VERSION);
333        if (v != null) {
334            env.setHostApplicationVersion((String) hostEnv.get(HOST_VERSION));
335        }
336
337        v = getEnvProperty(Environment.NUXEO_DATA_DIR, hostEnv, sysprops, true);
338        if (v != null) {
339            env.setData(new File(v));
340        } else {
341            sysprops.setProperty(Environment.NUXEO_DATA_DIR, env.getData().getAbsolutePath());
342        }
343
344        v = getEnvProperty(Environment.NUXEO_LOG_DIR, hostEnv, sysprops, true);
345        if (v != null) {
346            env.setLog(new File(v));
347        } else {
348            sysprops.setProperty(Environment.NUXEO_LOG_DIR, env.getLog().getAbsolutePath());
349        }
350
351        v = getEnvProperty(Environment.NUXEO_TMP_DIR, hostEnv, sysprops, true);
352        if (v != null) {
353            env.setTemp(new File(v));
354        } else {
355            sysprops.setProperty(Environment.NUXEO_TMP_DIR, env.getTemp().getAbsolutePath());
356        }
357
358        v = getEnvProperty(Environment.NUXEO_CONFIG_DIR, hostEnv, sysprops, true);
359        if (v != null) {
360            env.setConfig(new File(v));
361        } else {
362            sysprops.setProperty(Environment.NUXEO_CONFIG_DIR, env.getConfig().getAbsolutePath());
363        }
364
365        v = getEnvProperty(Environment.NUXEO_WEB_DIR, hostEnv, sysprops, true);
366        if (v != null) {
367            env.setWeb(new File(v));
368        } else {
369            sysprops.setProperty(Environment.NUXEO_WEB_DIR, env.getWeb().getAbsolutePath());
370        }
371
372        v = (String) hostEnv.get(ARGS);
373        if (v != null) {
374            env.setCommandLineArguments(v.split("\\s+"));
375        } else {
376            env.setCommandLineArguments(new String[0]);
377        }
378        return env;
379    }
380
381    protected static void printStartMessage() {
382        StringBuilder msg = getStartMessage();
383        log.info(msg);
384    }
385
386    /**
387     * @since 5.5
388     * @return Environment summary
389     */
390    protected static StringBuilder getStartMessage() {
391        String newline = System.getProperty("line.separator");
392        Environment env = Environment.getDefault();
393        String hr = "======================================================================";
394        StringBuilder msg = new StringBuilder(newline);
395        msg.append(hr).append(newline);
396        msg.append("= Starting Nuxeo Framework").append(newline);
397        msg.append(hr).append(newline);
398        msg.append("  * Server home = ").append(env.getServerHome()).append(newline);
399        msg.append("  * Runtime home = ").append(env.getRuntimeHome()).append(newline);
400        msg.append("  * Data Directory = ").append(env.getData()).append(newline);
401        msg.append("  * Log Directory = ").append(env.getLog()).append(newline);
402        msg.append("  * Configuration Directory = ").append(env.getConfig()).append(newline);
403        msg.append("  * Temp Directory = ").append(env.getTemp()).append(newline);
404        msg.append(hr);
405        return msg;
406    }
407}