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