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    public void start(MutableClassLoader cl) throws ReflectiveOperationException, IOException, JMException {
127
128    }
129
130    public void stop(MutableClassLoader cl) throws ReflectiveOperationException, JMException {
131
132    }
133
134    public String installBundle(File f) throws ReflectiveOperationException {
135        if (frameworkLoaderClass == null) {
136            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
137        }
138        Method install = frameworkLoaderClass.getMethod("install", File.class);
139        return (String) install.invoke(null, f);
140    }
141
142    public void uninstallBundle(String name) throws ReflectiveOperationException {
143        if (frameworkLoaderClass == null) {
144            throw new IllegalStateException("Framework Loader was not initialized. Call initialize() method first");
145        }
146        Method uninstall = frameworkLoaderClass.getMethod("uninstall", String.class);
147        uninstall.invoke(null, name);
148    }
149
150    @SuppressWarnings({ "unchecked", "rawtypes" })
151    protected void initializeEnvironment() throws IOException {
152        System.setProperty(HOME_DIR, home.getAbsolutePath());
153        env = new HashMap<>();
154        // initialize with default values
155        env.put(BUNDLES, DEFAULT_BUNDLES_CP);
156        env.put(LIBS, DEFAULT_LIBS_CP);
157        // load launcher.properties file if exists to overwrite default values
158        File file = new File(home, "launcher.properties");
159        if (!file.isFile()) {
160            return;
161        }
162        Properties p = new Properties();
163        try (FileInputStream in = new FileInputStream(file)) {
164            p.load(in);
165            env.putAll((Map) p);
166            String v = (String) env.get(SCAN_FOR_NESTED_JARS);
167            if (v != null) {
168                scanForNestedJars = Boolean.parseBoolean(v);
169            }
170            v = (String) env.get(FLUSH_CACHE);
171            if (v != null) {
172                flushCache = Boolean.parseBoolean(v);
173            }
174        }
175    }
176
177    protected void printStartedMessage() {
178        log.info("Framework started in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec.");
179    }
180
181    protected File newFile(String path) throws IOException {
182        if (path.startsWith("/")) {
183            return new File(path).getCanonicalFile();
184        } else {
185            return new File(home, path).getCanonicalFile();
186        }
187    }
188
189    /**
190     * Fills the classloader with all jars found in the defined classpath.
191     *
192     * @return the list of bundle files.
193     */
194    protected List<File> buildClassPath() throws IOException {
195        List<File> bundleFiles = new ArrayList<>();
196        String libsCp = (String) env.get(LIBS);
197        if (libsCp != null) {
198            buildLibsClassPath(libsCp);
199        }
200        String bundlesCp = (String) env.get(BUNDLES);
201        if (bundlesCp != null) {
202            buildBundlesClassPath(bundlesCp, bundleFiles);
203        }
204        extractNestedJars(bundleFiles, new File(home, "tmp/nested-jars"));
205        return bundleFiles;
206    }
207
208    protected void buildLibsClassPath(String libsCp) throws IOException {
209        String[] ar = libsCp.split(":");
210        for (String entry : ar) {
211            File entryFile;
212            if (entry.endsWith("/*")) {
213                entryFile = newFile(entry.substring(0, entry.length() - 2));
214                File[] files = entryFile.listFiles();
215                if (files != null) {
216                    for (File file : files) {
217                        loader.addURL(file.toURI().toURL());
218                    }
219                }
220            } else {
221                entryFile = newFile(entry);
222                loader.addURL(entryFile.toURI().toURL());
223            }
224        }
225    }
226
227    protected void buildBundlesClassPath(String bundlesCp, List<File> bundleFiles) throws IOException {
228        String[] ar = bundlesCp.split(":");
229        for (String entry : ar) {
230            File entryFile;
231            if (entry.endsWith("/*")) {
232                entryFile = newFile(entry.substring(0, entry.length() - 2));
233                File[] files = entryFile.listFiles();
234                if (files != null) {
235                    for (File file : files) {
236                        String path = file.getPath();
237                        if (path.endsWith(".jar") || path.endsWith(".zip") || path.endsWith(".war")
238                                || path.endsWith("rar")) {
239                            bundleFiles.add(file);
240                            loader.addURL(file.toURI().toURL());
241                        }
242                    }
243                }
244            } else {
245                entryFile = newFile(entry);
246                bundleFiles.add(entryFile);
247                loader.addURL(entryFile.toURI().toURL());
248            }
249        }
250    }
251
252    protected void extractNestedJars(List<File> bundleFiles, File dir) throws IOException {
253        if (!scanForNestedJars) {
254            return;
255        }
256        if (dir.isDirectory()) {
257            if (flushCache) {
258                deleteAll(dir);
259            } else {
260                File[] files = dir.listFiles();
261                if (files != null) {
262                    for (File f : files) {
263                        loader.addURL(f.toURI().toURL());
264                    }
265                }
266                return;
267            }
268        }
269        dir.mkdirs();
270        for (File f : bundleFiles) {
271            if (f.isFile()) {
272                extractNestedJars(f, dir);
273            }
274        }
275    }
276
277    protected void extractNestedJars(File file, File tmpDir) throws IOException {
278        try (JarFile jarFile = new JarFile(file)) {
279            String fileName = file.getName();
280            Enumeration<JarEntry> entries = jarFile.entries();
281            while (entries.hasMoreElements()) {
282                JarEntry entry = entries.nextElement();
283                String path = entry.getName();
284                if (entry.getName().endsWith(".jar")) {
285                    String name = path.replace('/', '_');
286                    File dest = new File(tmpDir, fileName + '-' + name);
287                    extractNestedJar(jarFile, entry, dest);
288                    loader.addURL(dest.toURI().toURL());
289                }
290            }
291        }
292    }
293
294    protected void extractNestedJar(JarFile file, ZipEntry entry, File dest) throws IOException {
295        try (InputStream in = file.getInputStream(entry)) {
296            copyToFile(in, dest);
297        }
298    }
299
300    public static void deleteAll(File file) {
301        if (file.isDirectory()) {
302            File[] files = file.listFiles();
303            if (files != null) {
304                for (File f : files) {
305                    deleteAll(f);
306                }
307            }
308        }
309        file.delete();
310    }
311
312    public static void copyFile(File src, File file) throws IOException {
313        try (FileInputStream in = new FileInputStream(src)) {
314            copyToFile(in, file);
315        }
316    }
317
318    public static void copyToFile(InputStream in, File file) throws IOException {
319        try (OutputStream out = new FileOutputStream(file)) {
320            byte[] buffer = createBuffer(in.available());
321            int read;
322            while ((read = in.read(buffer)) != -1) {
323                out.write(buffer, 0, read);
324            }
325        }
326    }
327
328    private static final int BUFFER_SIZE = 1024 * 64; // 64K
329
330    private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K
331
332    private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K
333
334    private static byte[] createBuffer(int preferredSize) {
335        if (preferredSize < 1) {
336            preferredSize = BUFFER_SIZE;
337        }
338        if (preferredSize > MAX_BUFFER_SIZE) {
339            preferredSize = MAX_BUFFER_SIZE;
340        } else if (preferredSize < MIN_BUFFER_SIZE) {
341            preferredSize = MIN_BUFFER_SIZE;
342        }
343        return new byte[preferredSize];
344    }
345
346    public static File findFileStartingWidth(File dir, String prefix) {
347        String[] names = dir.list();
348        if (names != null) {
349            for (String name : names) {
350                if (name.startsWith(prefix)) {
351                    return new File(dir, name);
352                }
353            }
354        }
355        return null;
356    }
357
358    public static void copyTree(File src, File dst) throws IOException {
359        if (src.isFile()) {
360            copyFile(src, dst);
361        } else if (src.isDirectory()) {
362            if (dst.exists()) {
363                dst = new File(dst, src.getName());
364                dst.mkdir();
365            } else { // allows renaming dest dir
366                dst.mkdirs();
367            }
368            File[] files = src.listFiles();
369            for (File file : files) {
370                copyTree(file, dst);
371            }
372        }
373    }
374
375}