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