001/*
002 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu, jcarsique
016 */
017package org.nuxeo.osgi.application;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.lang.reflect.Method;
026import java.util.ArrayList;
027import java.util.Enumeration;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Properties;
032import java.util.jar.JarEntry;
033import java.util.jar.JarFile;
034import java.util.zip.ZipEntry;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038
039/**
040 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
041 */
042public class FrameworkBootstrap implements LoaderConstants {
043
044    protected static final String DEFAULT_BUNDLES_CP = "bundles/*:plugins/*";
045
046    protected static final String DEFAULT_LIBS_CP = "lib/*:.:config";
047
048    private static final Log log = LogFactory.getLog(FrameworkBootstrap.class);
049
050    protected File home;
051
052    protected MutableClassLoader loader;
053
054    protected Map<String, Object> env;
055
056    protected Class<?> frameworkLoaderClass;
057
058    protected long startTime;
059
060    protected boolean scanForNestedJars = true;
061
062    protected boolean flushCache = false;
063
064    public FrameworkBootstrap(ClassLoader cl, File home) throws IOException {
065        this(new MutableClassLoaderDelegate(cl), home);
066    }
067
068    public FrameworkBootstrap(MutableClassLoader loader, File home) throws IOException {
069        this.home = home.getCanonicalFile();
070        this.loader = loader;
071        initializeEnvironment();
072    }
073
074    public void setHostName(String value) {
075        env.put(HOST_NAME, value);
076    }
077
078    public void setHostVersion(String value) {
079        env.put(HOST_VERSION, value);
080    }
081
082    public void setDoPreprocessing(boolean doPreprocessing) {
083        env.put(PREPROCESSING, Boolean.toString(doPreprocessing));
084    }
085
086    public void setDevMode(String devMode) {
087        env.put(DEVMODE, devMode);
088    }
089
090    public void setFlushCache(boolean flushCache) {
091        this.flushCache = flushCache;
092    }
093
094    public void setScanForNestedJars(boolean scanForNestedJars) {
095        this.scanForNestedJars = scanForNestedJars;
096    }
097
098    public Map<String, Object> env() {
099        return env;
100    }
101
102    public MutableClassLoader getLoader() {
103        return loader;
104    }
105
106    public ClassLoader getClassLoader() {
107        return loader.getClassLoader();
108    }
109
110    public File getHome() {
111        return home;
112    }
113
114    public void initialize() throws ReflectiveOperationException, IOException {
115        startTime = System.currentTimeMillis();
116        List<File> bundleFiles = buildClassPath();
117        frameworkLoaderClass = getClassLoader().loadClass("org.nuxeo.osgi.application.loader.FrameworkLoader");
118        Method init = frameworkLoaderClass.getMethod("initialize", ClassLoader.class, File.class, List.class, Map.class);
119        init.invoke(null, loader.getClassLoader(), home, bundleFiles, env);
120    }
121
122    public void start() throws ReflectiveOperationException, IOException {
123        if (frameworkLoaderClass == null) {
124            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
125        }
126        Method start = frameworkLoaderClass.getMethod("start");
127        start.invoke(null);
128        printStartedMessage();
129    }
130
131    public void stop() throws ReflectiveOperationException {
132        if (frameworkLoaderClass == null) {
133            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
134        }
135        Method stop = frameworkLoaderClass.getMethod("stop");
136        stop.invoke(null);
137    }
138
139    public String installBundle(File f) throws ReflectiveOperationException {
140        if (frameworkLoaderClass == null) {
141            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
142        }
143        Method install = frameworkLoaderClass.getMethod("install", File.class);
144        return (String) install.invoke(null, f);
145    }
146
147    public void uninstallBundle(String name) throws ReflectiveOperationException {
148        if (frameworkLoaderClass == null) {
149            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
150        }
151        Method uninstall = frameworkLoaderClass.getMethod("uninstall", String.class);
152        uninstall.invoke(null, name);
153    }
154
155    @SuppressWarnings({ "unchecked", "rawtypes" })
156    protected void initializeEnvironment() throws IOException {
157        System.setProperty(HOME_DIR, home.getAbsolutePath());
158        env = new HashMap<String, Object>();
159        // initialize with default values
160        env.put(BUNDLES, DEFAULT_BUNDLES_CP);
161        env.put(LIBS, DEFAULT_LIBS_CP);
162        // load launcher.properties file if exists to overwrite default values
163        File file = new File(home, "launcher.properties");
164        if (!file.isFile()) {
165            return;
166        }
167        Properties p = new Properties();
168        FileInputStream in = new FileInputStream(file);
169        try {
170            p.load(in);
171            env.putAll((Map) p);
172            String v = (String) env.get(SCAN_FOR_NESTED_JARS);
173            if (v != null) {
174                scanForNestedJars = Boolean.parseBoolean(v);
175            }
176            v = (String) env.get(FLUSH_CACHE);
177            if (v != null) {
178                flushCache = Boolean.parseBoolean(v);
179            }
180        } finally {
181            in.close();
182        }
183    }
184
185    protected void printStartedMessage() {
186        log.info("Framework started in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec.");
187    }
188
189    protected File newFile(String path) throws IOException {
190        if (path.startsWith("/")) {
191            return new File(path).getCanonicalFile();
192        } else {
193            return new File(home, path).getCanonicalFile();
194        }
195    }
196
197    /**
198     * Fills the classloader with all jars found in the defined classpath.
199     *
200     * @return the list of bundle files.
201     */
202    protected List<File> buildClassPath() throws IOException {
203        List<File> bundleFiles = new ArrayList<File>();
204        String libsCp = (String) env.get(LIBS);
205        if (libsCp != null) {
206            buildLibsClassPath(libsCp);
207        }
208        String bundlesCp = (String) env.get(BUNDLES);
209        if (bundlesCp != null) {
210            buildBundlesClassPath(bundlesCp, bundleFiles);
211        }
212        extractNestedJars(bundleFiles, new File(home, "tmp/nested-jars"));
213        return bundleFiles;
214    }
215
216    protected void buildLibsClassPath(String libsCp) throws IOException {
217        String[] ar = libsCp.split(":");
218        for (String entry : ar) {
219            File entryFile;
220            if (entry.endsWith("/*")) {
221                entryFile = newFile(entry.substring(0, entry.length() - 2));
222                File[] files = entryFile.listFiles();
223                if (files != null) {
224                    for (File file : files) {
225                        loader.addURL(file.toURI().toURL());
226                    }
227                }
228            } else {
229                entryFile = newFile(entry);
230                loader.addURL(entryFile.toURI().toURL());
231            }
232        }
233    }
234
235    protected void buildBundlesClassPath(String bundlesCp, List<File> bundleFiles) throws IOException {
236        String[] ar = bundlesCp.split(":");
237        for (String entry : ar) {
238            File entryFile;
239            if (entry.endsWith("/*")) {
240                entryFile = newFile(entry.substring(0, entry.length() - 2));
241                File[] files = entryFile.listFiles();
242                if (files != null) {
243                    for (File file : files) {
244                        String path = file.getPath();
245                        if (path.endsWith(".jar") || path.endsWith(".zip") || path.endsWith(".war")
246                                || path.endsWith("rar")) {
247                            bundleFiles.add(file);
248                            loader.addURL(file.toURI().toURL());
249                        }
250                    }
251                }
252            } else {
253                entryFile = newFile(entry);
254                bundleFiles.add(entryFile);
255                loader.addURL(entryFile.toURI().toURL());
256            }
257        }
258    }
259
260    protected void extractNestedJars(List<File> bundleFiles, File dir) throws IOException {
261        if (!scanForNestedJars) {
262            return;
263        }
264        if (dir.isDirectory()) {
265            if (flushCache) {
266                deleteAll(dir);
267            } else {
268                File[] files = dir.listFiles();
269                if (files != null) {
270                    for (File f : files) {
271                        loader.addURL(f.toURI().toURL());
272                    }
273                }
274                return;
275            }
276        }
277        dir.mkdirs();
278        for (File f : bundleFiles) {
279            if (f.isFile()) {
280                extractNestedJars(f, dir);
281            }
282        }
283    }
284
285    protected void extractNestedJars(File file, File tmpDir) throws IOException {
286        JarFile jarFile = new JarFile(file);
287        String fileName = file.getName();
288        Enumeration<JarEntry> entries = jarFile.entries();
289        while (entries.hasMoreElements()) {
290            JarEntry entry = entries.nextElement();
291            String path = entry.getName();
292            if (entry.getName().endsWith(".jar")) {
293                String name = path.replace('/', '_');
294                File dest = new File(tmpDir, fileName + '-' + name);
295                extractNestedJar(jarFile, entry, dest);
296                loader.addURL(dest.toURI().toURL());
297            }
298        }
299    }
300
301    protected void extractNestedJar(JarFile file, ZipEntry entry, File dest) throws IOException {
302        InputStream in = null;
303        try {
304            in = file.getInputStream(entry);
305            copyToFile(in, dest);
306        } finally {
307            if (in != null) {
308                in.close();
309            }
310        }
311    }
312
313    public static void deleteAll(File file) {
314        if (file.isDirectory()) {
315            File[] files = file.listFiles();
316            if (files != null) {
317                for (File f : files) {
318                    deleteAll(f);
319                }
320            }
321        }
322        file.delete();
323    }
324
325    public static void copyFile(File src, File file) throws IOException {
326        FileInputStream in = new FileInputStream(src);
327        try {
328            copyToFile(in, file);
329        } finally {
330            in.close();
331        }
332    }
333
334    public static void copyToFile(InputStream in, File file) throws IOException {
335        OutputStream out = null;
336        try {
337            out = new FileOutputStream(file);
338            byte[] buffer = createBuffer(in.available());
339            int read;
340            while ((read = in.read(buffer)) != -1) {
341                out.write(buffer, 0, read);
342            }
343        } finally {
344            if (out != null) {
345                out.close();
346            }
347        }
348    }
349
350    private static final int BUFFER_SIZE = 1024 * 64; // 64K
351
352    private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K
353
354    private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K
355
356    private static byte[] createBuffer(int preferredSize) {
357        if (preferredSize < 1) {
358            preferredSize = BUFFER_SIZE;
359        }
360        if (preferredSize > MAX_BUFFER_SIZE) {
361            preferredSize = MAX_BUFFER_SIZE;
362        } else if (preferredSize < MIN_BUFFER_SIZE) {
363            preferredSize = MIN_BUFFER_SIZE;
364        }
365        return new byte[preferredSize];
366    }
367
368    public static File findFileStartingWidth(File dir, String prefix) {
369        String[] names = dir.list();
370        if (names != null) {
371            for (String name : names) {
372                if (name.startsWith(prefix)) {
373                    return new File(dir, name);
374                }
375            }
376        }
377        return null;
378    }
379
380    public static void copyTree(File src, File dst) throws IOException {
381        if (src.isFile()) {
382            copyFile(src, dst);
383        } else if (src.isDirectory()) {
384            if (dst.exists()) {
385                dst = new File(dst, src.getName());
386                dst.mkdir();
387            } else { // allows renaming dest dir
388                dst.mkdirs();
389            }
390            File[] files = src.listFiles();
391            for (File file : files) {
392                copyTree(file, dst);
393            }
394        }
395    }
396
397}