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.directory.api.util;
021
022
023import java.io.File;
024import java.io.FileFilter;
025import java.io.IOException;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029import java.util.jar.JarFile;
030import java.util.jar.Manifest;
031
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035
036/**
037 * Utilities for OSGi environments and embedding OSGi containers.
038 *
039 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
040 */
041public final class OsgiUtils
042{
043    /** A logger */
044    private static final Logger LOG = LoggerFactory.getLogger( OsgiUtils.class );
045
046
047    /**
048     * All the packages that are exported from all bundles found on the system
049     * classpath. The provided filter if not null is used to prune classpath
050     * elements. Any uses terms found are stripped from the bundles.
051     *
052     * @param filter The filter to use on the files
053     * @param pkgs The set of packages to use
054     * @return All the exported packages of all bundles on the classpath.
055     */
056    public static Set<String> getAllBundleExports( FileFilter filter, Set<String> pkgs )
057    {
058        if ( pkgs == null )
059        {
060            pkgs = new HashSet<String>();
061        }
062
063        Set<File> candidates = getClasspathCandidates( filter );
064
065        for ( File candidate : candidates )
066        {
067            String exports = getBundleExports( candidate );
068
069            if ( exports == null )
070            {
071                LOG.debug( "No export found for candidate: {}", candidate );
072                continue;
073            }
074
075            LOG.debug( "Processing exports for candidate: {}\n\n{}\n", candidate, exports );
076            splitIntoPackages( exports, pkgs );
077        }
078
079        return pkgs;
080    }
081
082
083    /**
084     * Splits a Package-Export OSGi Manifest Attribute value into packages
085     * while stripping away the key/value properties.
086     *
087     * @param exports The Package-Export OSGi Manifest Attribute value.
088     * @param pkgs The set that will contain the found packages.
089     * @return The set of exported packages without properties.
090     */
091    public static Set<String> splitIntoPackages( String exports, Set<String> pkgs )
092    {
093        if ( pkgs == null )
094        {
095            pkgs = new HashSet<String>();
096        }
097
098        int index = 0;
099        boolean inPkg = true;
100        boolean inProps = false;
101        StringBuilder pkg = new StringBuilder();
102
103        while ( index < exports.length() )
104        {
105            if ( inPkg && exports.charAt( index ) != ';' )
106            {
107                pkg.append( exports.charAt( index ) );
108                index++;
109            }
110            else if ( inPkg && exports.charAt( index ) == ';' )
111            {
112                inPkg = false;
113                inProps = true;
114
115                pkgs.add( pkg.toString() );
116                LOG.debug( "Added package: {}", pkg.toString() );
117                pkg.setLength( 0 );
118
119                index += 8;
120            }
121            else if ( inProps && exports.charAt( index ) == '"'
122                && index + 1 < exports.length()
123                && exports.charAt( index + 1 ) == ',' )
124            {
125                inPkg = true;
126                inProps = false;
127                index += 2;
128            }
129            else if ( inProps )
130            {
131                index++;
132            }
133            else
134            {
135                LOG.error( "Unexpected parser condition throwing IllegalStateException." );
136                throw new IllegalStateException( "Should never get here!" );
137            }
138        }
139
140        return pkgs;
141    }
142
143
144    /**
145     * Get the files that fits a given filter
146     *
147     * @param filter The filter in use
148     * @return The set of Files that match the filter
149     */
150    public static Set<File> getClasspathCandidates( FileFilter filter )
151    {
152        Set<File> candidates = new HashSet<File>();
153        String separator = System.getProperty( "path.separator" );
154        String[] cpElements = System.getProperty( "java.class.path" ).split( separator );
155
156        for ( String element : cpElements )
157        {
158            File candidate = new File( element );
159
160            if ( candidate.isFile() )
161            {
162                if ( filter != null && filter.accept( candidate ) )
163                {
164                    candidates.add( candidate );
165                    LOG.info( "Accepted candidate with filter: {}", candidate.toString() );
166                }
167                else if ( filter == null && candidate.getName().endsWith( ".jar" ) )
168                {
169                    candidates.add( candidate );
170                    LOG.info( "Accepted candidate without filter: {}", candidate.toString() );
171                }
172                else
173                {
174                    LOG.info( "Rejecting candidate: {}", candidate.toString() );
175                }
176            }
177        }
178
179        return candidates;
180    }
181
182
183    /**
184     * Gets the attribute value for the Export-Bundle OSGi Manifest Attribute.
185     * 
186     * @param bundle The absolute path to a file bundle.
187     * @return The value as it appears in the Manifest, as a comma delimited
188     * list of packages with possible "uses" phrases appended to each package
189     * or null if the attribute does not exist.
190     */
191    public static String getBundleExports( File bundle )
192    {
193        try ( JarFile jar = new JarFile( bundle ) )
194        {
195            Manifest manifest = jar.getManifest();
196
197            if ( manifest == null )
198            {
199                return null;
200            }
201
202            for ( Map.Entry<Object, Object> attr : manifest.getMainAttributes().entrySet() )
203            {
204                if ( attr.getKey().toString().equals( "Export-Package" ) )
205                {
206                    return attr.getValue().toString();
207                }
208            }
209
210            return null;
211        }
212        catch ( IOException e )
213        {
214            LOG.error( "Failed to open jar file or manifest.", e );
215            throw new RuntimeException( "Failed to open jar file or manifest.", e );
216        }
217    }
218
219
220    private OsgiUtils()
221    {
222    }
223}