    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.maven.shared.osgi;
    20
    21
    22  import java.io.File;
    23  import java.io.IOException;
    24  import java.util.Enumeration;
    25  import java.util.HashSet;
    26  import java.util.Iterator;
    27  import java.util.Map;
    28  import java.util.Set;
    29  import java.util.jar.JarFile;
    30  import java.util.regex.Matcher;
    31  import java.util.regex.Pattern;
    32  import java.util.zip.ZipEntry;
    33
    34  import org.apache.maven.artifact.Artifact;
    35
    36  import aQute.bnd.osgi.Analyzer;
    37
    38
    39  /**
    40   * Default implementation of {@link Maven2OsgiConverter}
    41   *
    42   * @plexus.component
    43   *
    44   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
    45   * @version $Id: DefaultMaven2OsgiConverter.html 1490866 2013-06-07 23:25:18Z mcculls $
    46   */
    47  public class DefaultMaven2OsgiConverter implements Maven2OsgiConverter
    48  {
    49
    50      private static final String FILE_SEPARATOR = System.getProperty( "file.separator" );
    51
    52
    53      private String getBundleSymbolicName( String groupId, String artifactId )
    54      {
    55          return groupId + "." + artifactId;
    56      }
    57
    58
    59      /**
    60       * Get the symbolic name as groupId + "." + artifactId, with the following exceptions
    61       * <ul>
    62       * <li>if artifact.getFile is not null and the jar contains a OSGi Manifest with
    63       * Bundle-SymbolicName property then that value is returned</li>
    64       * <li>if groupId has only one section (no dots) and artifact.getFile is not null then the
    65       * first package name with classes is returned. eg. commons-logging:commons-logging ->
    66       * org.apache.commons.logging</li>
    67       * <li>if artifactId is equal to last section of groupId then groupId is returned. eg.
    68       * org.apache.maven:maven -> org.apache.maven</li>
    69       * <li>if artifactId starts with last section of groupId that portion is removed. eg.
    70       * org.apache.maven:maven-core -> org.apache.maven.core</li>
    71       * <li>if artifactId starts with groupId then the artifactId is removed. eg.
    72       * org.apache:org.apache.maven.core -> org.apache.maven.core</li>
    73       * </ul>
    74       */
    75      public String getBundleSymbolicName( Artifact artifact )
    76      {
    77          if ( ( artifact.getFile() != null ) && artifact.getFile().isFile() )
    78          {
    79              Analyzer analyzer = new Analyzer();
    80
    81              JarFile jar = null;
    82              try
    83              {
    84                  jar = new JarFile( artifact.getFile(), false );
    85
    86                  if ( jar.getManifest() != null )
    87                  {
    88                      String symbolicNameAttribute = jar.getManifest().getMainAttributes()
    89                          .getValue( Analyzer.BUNDLE_SYMBOLICNAME );
    90                      Map bundleSymbolicNameHeader = analyzer.parseHeader( symbolicNameAttribute );
    91
    92                      Iterator it = bundleSymbolicNameHeader.keySet().iterator();
    93                      if ( it.hasNext() )
    94                      {
    95                          return ( String ) it.next();
    96                      }
    97                  }
    98              }
    99              catch ( IOException e )
    100             {
    101                 throw new ManifestReadingException( "Error reading manifest in jar "
    102                     + artifact.getFile().getAbsolutePath(), e );
    103             }
    104             finally
    105             {
    106                 if ( jar != null )
    107                 {
    108                     try
    109                     {
    110                         jar.close();
    111                     }
    112                     catch ( IOException e )
    113                     {
    114                     }
    115                 }
    116             }
    117         }
    118
    119         int i = artifact.getGroupId().lastIndexOf( '.' );
    120         if ( ( i < 0 ) && ( artifact.getFile() != null ) && artifact.getFile().isFile() )
    121         {
    122             String groupIdFromPackage = getGroupIdFromPackage( artifact.getFile() );
    123             if ( groupIdFromPackage != null )
    124             {
    125                 return groupIdFromPackage;
    126             }
    127         }
    128         String lastSection = artifact.getGroupId().substring( ++i );
    129         if ( artifact.getArtifactId().equals( lastSection ) )
    130         {
    131             return artifact.getGroupId();
    132         }
    133         if ( artifact.getArtifactId().equals( artifact.getGroupId() )
    134             || artifact.getArtifactId().startsWith( artifact.getGroupId() + "." ) )
    135         {
    136             return artifact.getArtifactId();
    137         }
    138         if ( artifact.getArtifactId().startsWith( lastSection ) )
    139         {
    140             String artifactId = artifact.getArtifactId().substring( lastSection.length() );
    141             if ( Character.isLetterOrDigit( artifactId.charAt( 0 ) ) )
    142             {
    143                 return getBundleSymbolicName( artifact.getGroupId(), artifactId );
    144             }
    145             else
    146             {
    147                 return getBundleSymbolicName( artifact.getGroupId(), artifactId.substring( 1 ) );
    148             }
    149         }
    150         return getBundleSymbolicName( artifact.getGroupId(), artifact.getArtifactId() );
    151     }
    152
    153
    154     private String getGroupIdFromPackage( File artifactFile )
    155     {
    156         try
    157         {
    158             /* get package names from jar */
    159             Set packageNames = new HashSet();
    160             JarFile jar = new JarFile( artifactFile, false );
    161             Enumeration entries = jar.entries();
    162             while ( entries.hasMoreElements() )
    163             {
    164                 ZipEntry entry = ( ZipEntry ) entries.nextElement();
    165                 if ( entry.getName().endsWith( ".class" ) )
    166                 {
    167                     File f = new File( entry.getName() );
    168                     String packageName = f.getParent();
    169                     if ( packageName != null )
    170                     {
    171                         packageNames.add( packageName );
    172                     }
    173                 }
    174             }
    175             jar.close();
    176
    177             /* find the top package */
    178             String[] groupIdSections = null;
    179             for ( Iterator it = packageNames.iterator(); it.hasNext(); )
    180             {
    181                 String packageName = ( String ) it.next();
    182
    183                 String[] packageNameSections = packageName.split( "\\" + FILE_SEPARATOR );
    184                 if ( groupIdSections == null )
    185                 {
    186                     /* first candidate */
    187                     groupIdSections = packageNameSections;
    188                 }
    189                 else
    190                 // if ( packageNameSections.length < groupIdSections.length )
    191                 {
    192                     /*
    193                      * find the common portion of current package and previous selected groupId
    194                      */
    195                     int i;
    196                     for ( i = 0; ( i < packageNameSections.length ) && ( i < groupIdSections.length ); i++ )
    197                     {
    198                         if ( !packageNameSections[i].equals( groupIdSections[i] ) )
    199                         {
    200                             break;
    201                         }
    202                     }
    203                     groupIdSections = new String[i];
    204                     System.arraycopy( packageNameSections, 0, groupIdSections, 0, i );
    205                 }
    206             }
    207
    208             if ( ( groupIdSections == null ) || ( groupIdSections.length == 0 ) )
    209             {
    210                 return null;
    211             }
    212
    213             /* only one section as id doesn't seem enough, so ignore it */
    214             if ( groupIdSections.length == 1 )
    215             {
    216                 return null;
    217             }
    218
    219             StringBuffer sb = new StringBuffer();
    220             for ( int i = 0; i < groupIdSections.length; i++ )
    221             {
    222                 sb.append( groupIdSections[i] );
    223                 if ( i < groupIdSections.length - 1 )
    224                 {
    225                     sb.append( '.' );
    226                 }
    227             }
    228             return sb.toString();
    229         }
    230         catch ( IOException e )
    231         {
    232             /* we took all the precautions to avoid this */
    233             throw new RuntimeException( e );
    234         }
    235     }
    236
    237
    238     public String getBundleFileName( Artifact artifact )
    239     {
    240         return getBundleSymbolicName( artifact ) + "_" + getVersion( artifact.getVersion() ) + ".jar";
    241     }
    242
    243
    244     public String getVersion( Artifact artifact )
    245     {
    246         return getVersion( artifact.getVersion() );
    247     }
    248
    249
    250     public String getVersion( String version )
    251     {
    252         return cleanupVersion( version );
    253     }
    254
    255     /**
    256      * Clean up version parameters. Other builders use more fuzzy definitions of
    257      * the version syntax. This method cleans up such a version to match an OSGi
    258      * version.
    259      *
    260      * @param VERSION_STRING
    261      * @return
    262      */
    263     static final Pattern FUZZY_VERSION = Pattern.compile( "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
    264         Pattern.DOTALL );
    265
    266
    267     static public String cleanupVersion( String version )
    268     {
    269         StringBuffer result = new StringBuffer();
    270         Matcher m = FUZZY_VERSION.matcher( version );
    271         if ( m.matches() )
    272         {
    273             String major = m.group( 1 );
    274             String minor = m.group( 3 );
    275             String micro = m.group( 5 );
    276             String qualifier = m.group( 7 );
    277
    278             if ( major != null )
    279             {
    280                 result.append( major );
    281                 if ( minor != null )
    282                 {
    283                     result.append( "." );
    284                     result.append( minor );
    285                     if ( micro != null )
    286                     {
    287                         result.append( "." );
    288                         result.append( micro );
    289                         if ( qualifier != null )
    290                         {
    291                             result.append( "." );
    292                             cleanupModifier( result, qualifier );
    293                         }
    294                     }
    295                     else if ( qualifier != null )
    296                     {
    297                         result.append( ".0." );
    298                         cleanupModifier( result, qualifier );
    299                     }
    300                     else
    301                     {
    302                         result.append( ".0" );
    303                     }
    304                 }
    305                 else if ( qualifier != null )
    306                 {
    307                     result.append( ".0.0." );
    308                     cleanupModifier( result, qualifier );
    309                 }
    310                 else
    311                 {
    312                     result.append( ".0.0" );
    313                 }
    314             }
    315         }
    316         else
    317         {
    318             result.append( "0.0.0." );
    319             cleanupModifier( result, version );
    320         }
    321         return result.toString();
    322     }
    323
    324
    325     static void cleanupModifier( StringBuffer result, String modifier )
    326     {
    327         for ( int i = 0; i < modifier.length(); i++ )
    328         {
    329             char c = modifier.charAt( i );
    330             if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_'
    331                 || c == '-' )
    332                 result.append( c );
    333             else
    334                 result.append( '_' );
    335         }
    336     }
    337
    338 }