001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *  http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.xbean.osgi.bundle.util;
021
022import java.io.File;
023import java.io.IOException;
024import java.net.MalformedURLException;
025import java.net.URL;
026import java.util.Collections;
027import java.util.Dictionary;
028import java.util.Enumeration;
029import java.util.LinkedHashSet;
030import java.util.List;
031
032import org.osgi.framework.Bundle;
033import org.osgi.framework.BundleReference;
034import org.osgi.framework.Constants;
035import org.osgi.framework.ServiceReference;
036import org.osgi.framework.wiring.BundleRevision;
037import org.osgi.framework.wiring.BundleWire;
038import org.osgi.framework.wiring.BundleWiring;
039import org.osgi.service.packageadmin.ExportedPackage;
040import org.osgi.service.packageadmin.PackageAdmin;
041
042/**
043 * @version $Rev: 1347954 $ $Date: 2012-06-08 11:08:40 +0200 (Fri, 08 Jun 2012) $
044 */
045public class BundleUtils {
046    
047    private static final boolean isOSGi43 = isOSGi43();
048    
049    private static boolean isOSGi43() {
050        try {
051            Class.forName("org.osgi.framework.wiring.BundleWiring");
052            return true;
053        } catch (Throwable e) {
054            return false;
055        }
056    }
057
058    public final static String REFERENCE_SCHEME = "reference:";
059
060    public final static String FILE_SCHEMA = "file:";
061
062    public final static String REFERENCE_FILE_SCHEMA = "reference:file:";
063    
064    /**
065     *  Based on the constant field values, if it is bigger than the RESOLVED status value, the bundle has been resolved by the framework
066     * @param bundle
067     * @return true if the bundle is resolved, or false if not.
068     */
069    public static boolean isResolved(Bundle bundle) {
070        return bundle.getState() >= Bundle.RESOLVED;
071    }
072
073    /**
074     * resolve method will try to load the Object.class, the behavior triggers a resolved request to the OSGI framework.
075     * @param bundle
076     */
077    public static void resolve(Bundle bundle) {
078        if (isFragment(bundle)) {
079            return;
080        }
081        try {
082            bundle.loadClass(Object.class.getName());
083        } catch (Exception e) {
084        }
085    }
086
087    /**
088     * If the bundle fulfills the conditions below, it could be started
089     * a. Not in the UNINSTALLED status.
090     * b. Not in the STARTING status.
091     * c. Not a fragment bundle.
092     * @param bundle
093     * @return
094     */
095    public static boolean canStart(Bundle bundle) {
096        return (bundle.getState() != Bundle.UNINSTALLED) && (bundle.getState() != Bundle.STARTING) && (!isFragment(bundle));
097    }
098
099    /**
100     * If the bundle fulfills the conditions below, it could be stopped
101     * a. Not in the UNINSTALLED status.
102     * b. Not in the STOPPING status.
103     * c. Not a fragment bundle.
104     * @param bundle
105     * @return
106     */
107    public static boolean canStop(Bundle bundle) {
108        return (bundle.getState() != Bundle.UNINSTALLED) && (bundle.getState() != Bundle.STOPPING) && (!isFragment(bundle));
109    }
110
111    /**
112     * If the bundle fulfills the conditions below, it could be un-installed
113     * a. Not in the UNINSTALLED status.
114     * @param bundle
115     * @return
116     */
117    public static boolean canUninstall(Bundle bundle) {
118        return bundle.getState() != Bundle.UNINSTALLED;
119    }
120
121    public static boolean isFragment(Bundle bundle) {
122        Dictionary headers = bundle.getHeaders();
123        return (headers != null && headers.get(Constants.FRAGMENT_HOST) != null);
124    }
125
126    /**
127     * Returns bundle (if any) associated with current thread's context classloader.
128     * Invoking this method is equivalent to getBundle(Thread.currentThread().getContextClassLoader(), unwrap)
129     */
130    public static Bundle getContextBundle(boolean unwrap) {
131        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
132        return classLoader == null ? null : getBundle(classLoader, unwrap);
133    }
134
135    /**
136     *  Returns bundle (if any) associated with the classloader.
137     * @param classLoader
138     * @param unwrap if true and if the bundle associated with the context classloader is a
139     *        {@link DelegatingBundle}, this function will return the main application bundle
140     *        backing the {@link DelegatingBundle}. Otherwise, the bundle associated with
141     *        the context classloader is returned as is. See {@link BundleClassLoader#getBundle(boolean)}
142     *        for more information.
143     * @return The bundle associated with the classloader. Might be null.
144     */
145    public static Bundle getBundle(ClassLoader classLoader, boolean unwrap) {
146        if (classLoader instanceof DelegatingBundleReference) {
147            return ((DelegatingBundleReference) classLoader).getBundle(unwrap);
148        } else if (classLoader instanceof BundleReference) {
149            return ((BundleReference) classLoader).getBundle();
150        } else {
151            return null;
152        }
153    }
154
155    /**
156     * If the given bundle is a {@link DelegatingBundle} this function will return the main
157     * application bundle backing the {@link DelegatingBundle}. Otherwise, the bundle
158     * passed in is returned as is.
159     */
160    public static Bundle unwrapBundle(Bundle bundle) {
161        if (bundle instanceof DelegatingBundle) {
162            return ((DelegatingBundle) bundle).getMainBundle();
163        }
164        return bundle;
165    }
166
167    /**
168     * Works like {@link Bundle#getEntryPaths(String)} but also returns paths
169     * in attached fragment bundles.
170     *
171     * @param bundle
172     * @param name
173     * @return
174     */
175    public static Enumeration<String> getEntryPaths(Bundle bundle, String name) {
176        Enumeration<URL> entries = bundle.findEntries(name, null, false);
177        if (entries == null) {
178            return null;
179        }
180        LinkedHashSet<String> paths = new LinkedHashSet<String>();
181        while (entries.hasMoreElements()) {
182            URL url = entries.nextElement();
183            String path = url.getPath();
184            if (path.startsWith("/")) {
185                path = path.substring(1);
186            }
187            paths.add(path);
188        }
189        return Collections.enumeration(paths);
190    }
191
192    /**
193     * 1, If the bundle was installed with reference directory mode
194     * return the file URL directly.  
195     * 2, For traditional package bundle, Works like {@link Bundle#getEntry(String)} 
196     * 
197     * In addition to the searching abaove, it also checks attached fragment bundles for the given entry.
198     *
199     * @param bundle
200     * @param name
201     * @return
202     * @throws MalformedURLException 
203     */
204    public static URL getEntry(Bundle bundle, String name) throws MalformedURLException {
205        
206        if (name.endsWith("/")) {
207            name = name.substring(0, name.length() - 1);
208        }       
209        
210        File bundleFile = toFile(bundle);
211        if (bundleFile != null && bundleFile.isDirectory()) {
212            File entryFile = new File(bundleFile, name);
213            if (entryFile.exists()) {
214                                return entryFile.toURI().toURL();
215            } 
216        }
217        
218        if (name.equals("/")) {
219            return bundle.getEntry(name);
220        } 
221        
222        String path;
223        String pattern;
224        int pos = name.lastIndexOf("/");
225        if (pos == -1) {
226            path = "/";
227            pattern = name;
228        } else if (pos == 0) {
229            path = "/";
230            pattern = name.substring(1);
231        } else {
232            path = name.substring(0, pos);
233            pattern = name.substring(pos + 1);
234        }
235        Enumeration<URL> entries = bundle.findEntries(path, pattern, false);
236        if (entries != null && entries.hasMoreElements()) {
237            return entries.nextElement();
238        } else {
239            return null;
240        }
241    }
242    
243    public static URL getNestedEntry(Bundle bundle, String jarEntryName, String subEntryName) throws MalformedURLException {
244        File bundleFile = toFile(bundle);
245        if (bundleFile != null && bundleFile.isDirectory()) {
246            File entryFile = new File(bundleFile, jarEntryName);
247            if (entryFile.exists()) {
248                if (entryFile.isFile()) {
249                    return new URL("jar:" + entryFile.toURI().toURL() + "!/" + subEntryName);
250                } else {
251                    return new File(entryFile, subEntryName).toURI().toURL();
252                }
253            }
254            return null;
255        }
256        return new URL("jar:" + bundle.getEntry(jarEntryName).toString() + "!/" + subEntryName);
257    }    
258    
259
260    public static File toFile(Bundle bundle) {
261        return toFile(bundle.getLocation());
262    }
263
264    public static File toFile(URL url) {
265        return toFile(url.toExternalForm());
266    }
267    
268    /**
269     * Translate the reference:file:// style URL  to the underlying file instance
270     * @param url
271     * @return
272     */
273    public static File toFile(String url) {
274        if (url !=null && url.startsWith(REFERENCE_FILE_SCHEMA)) {
275            File file = null;
276            try {
277                file = new File(new URL(url.substring(REFERENCE_SCHEME.length())).toURI());
278                if (file.exists()) {
279                    return file;
280                }
281            } catch (Exception e) {
282                // If url includes special chars: { } [ ] % < > # ^ ?
283                // URISyntaxException or MalformedURLException will be thrown, 
284                // so try to use File(String) directly
285                file = new File(url.substring(REFERENCE_FILE_SCHEMA.length()));
286                if (file.exists()) {
287                    return file;
288                }
289            }
290        }
291        return null;
292    }
293
294    public static String toReferenceFileLocation(File file) throws IOException {
295        if (!file.exists()) {
296            throw new IOException("file not exist " + file.getAbsolutePath());
297        }
298        return REFERENCE_SCHEME + file.toURI();
299    }
300
301
302    public static LinkedHashSet<Bundle> getWiredBundles(Bundle bundle) {
303        if (isOSGi43) {
304            return getWiredBundles43(bundle);
305        } else {
306            return getWiredBundles42(bundle);
307        }
308    }
309    
310    private static LinkedHashSet<Bundle> getWiredBundles42(Bundle bundle) {
311        ServiceReference reference = bundle.getBundleContext().getServiceReference(PackageAdmin.class.getName());
312        PackageAdmin packageAdmin = (PackageAdmin) bundle.getBundleContext().getService(reference);
313        try {
314            return getWiredBundles(packageAdmin, bundle);
315        } finally {
316            bundle.getBundleContext().ungetService(reference);
317        }
318    }
319    
320    public static LinkedHashSet<Bundle> getWiredBundles(PackageAdmin packageAdmin, Bundle bundle) {
321        BundleDescription description = new BundleDescription(bundle.getHeaders());
322        // handle static wire via Import-Package
323        List<BundleDescription.ImportPackage> imports = description.getExternalImports();
324        LinkedHashSet<Bundle> wiredBundles = new LinkedHashSet<Bundle>();
325        for (BundleDescription.ImportPackage packageImport : imports) {
326            ExportedPackage[] exports = packageAdmin.getExportedPackages(packageImport.getName());
327            Bundle wiredBundle = getWiredBundle(bundle, exports);
328            if (wiredBundle != null) {
329                wiredBundles.add(wiredBundle);
330            }
331        }
332        // handle dynamic wire via DynamicImport-Package
333        if (!description.getDynamicImportPackage().isEmpty()) {
334            for (Bundle b : bundle.getBundleContext().getBundles()) {
335                if (!wiredBundles.contains(b)) {
336                    ExportedPackage[] exports = packageAdmin.getExportedPackages(b);
337                    Bundle wiredBundle = getWiredBundle(bundle, exports);
338                    if (wiredBundle != null) {
339                        wiredBundles.add(wiredBundle);
340                    }
341                }
342            }
343        }
344        return wiredBundles;
345    }
346
347    static Bundle getWiredBundle(Bundle bundle, ExportedPackage[] exports) {
348        if (exports != null) {
349            for (ExportedPackage exportedPackage : exports) {
350                Bundle[] importingBundles = exportedPackage.getImportingBundles();
351                if (importingBundles != null) {
352                    for (Bundle importingBundle : importingBundles) {
353                        if (importingBundle == bundle) {
354                            return exportedPackage.getExportingBundle();
355                        }
356                    }
357                }
358            }
359        }
360        return null;
361    }
362    
363    // OSGi 4.3 API
364    
365    private static LinkedHashSet<Bundle> getWiredBundles43(Bundle bundle) {
366        LinkedHashSet<Bundle> wiredBundles = new LinkedHashSet<Bundle>();
367        BundleWiring wiring = bundle.adapt(BundleWiring.class);
368        if (wiring != null) {
369            List<BundleWire> wires;
370            wires = wiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
371            for (BundleWire wire : wires) {
372                wiredBundles.add(wire.getProviderWiring().getBundle());
373            }
374            wires = wiring.getRequiredWires(BundleRevision.BUNDLE_NAMESPACE);
375            for (BundleWire wire : wires) {
376                wiredBundles.add(wire.getProviderWiring().getBundle());
377            }
378        }        
379        return wiredBundles;
380    }
381    
382}