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