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.lib.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 1143000 2011-07-05 11:50:48Z 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       * </ul>
72       */
73      public String getBundleSymbolicName( Artifact artifact )
74      {
75          if ( ( artifact.getFile() != null ) && artifact.getFile().isFile() )
76          {
77              Analyzer analyzer = new Analyzer();
78  
79              JarFile jar = null;
80              try
81              {
82                  jar = new JarFile( artifact.getFile(), false );
83  
84                  if ( jar.getManifest() != null )
85                  {
86                      String symbolicNameAttribute = jar.getManifest().getMainAttributes()
87                          .getValue( Analyzer.BUNDLE_SYMBOLICNAME );
88                      Map bundleSymbolicNameHeader = analyzer.parseHeader( symbolicNameAttribute );
89  
90                      Iterator it = bundleSymbolicNameHeader.keySet().iterator();
91                      if ( it.hasNext() )
92                      {
93                          return ( String ) it.next();
94                      }
95                  }
96              }
97              catch ( IOException e )
98              {
99                  throw new ManifestReadingException( "Error reading manifest in jar "
100                     + artifact.getFile().getAbsolutePath(), e );
101             }
102             finally
103             {
104                 if ( jar != null )
105                 {
106                     try
107                     {
108                         jar.close();
109                     }
110                     catch ( IOException e )
111                     {
112                     }
113                 }
114             }
115         }
116 
117         int i = artifact.getGroupId().lastIndexOf( '.' );
118         if ( ( i < 0 ) && ( artifact.getFile() != null ) && artifact.getFile().isFile() )
119         {
120             String groupIdFromPackage = getGroupIdFromPackage( artifact.getFile() );
121             if ( groupIdFromPackage != null )
122             {
123                 return groupIdFromPackage;
124             }
125         }
126         String lastSection = artifact.getGroupId().substring( ++i );
127         if ( artifact.getArtifactId().equals( lastSection ) )
128         {
129             return artifact.getGroupId();
130         }
131         if ( artifact.getArtifactId().startsWith( lastSection ) )
132         {
133             String artifactId = artifact.getArtifactId().substring( lastSection.length() );
134             if ( Character.isLetterOrDigit( artifactId.charAt( 0 ) ) )
135             {
136                 return getBundleSymbolicName( artifact.getGroupId(), artifactId );
137             }
138             else
139             {
140                 return getBundleSymbolicName( artifact.getGroupId(), artifactId.substring( 1 ) );
141             }
142         }
143         return getBundleSymbolicName( artifact.getGroupId(), artifact.getArtifactId() );
144     }
145 
146 
147     private String getGroupIdFromPackage( File artifactFile )
148     {
149         try
150         {
151             /* get package names from jar */
152             Set packageNames = new HashSet();
153             JarFile jar = new JarFile( artifactFile, false );
154             Enumeration entries = jar.entries();
155             while ( entries.hasMoreElements() )
156             {
157                 ZipEntry entry = ( ZipEntry ) entries.nextElement();
158                 if ( entry.getName().endsWith( ".class" ) )
159                 {
160                     File f = new File( entry.getName() );
161                     String packageName = f.getParent();
162                     if ( packageName != null )
163                     {
164                         packageNames.add( packageName );
165                     }
166                 }
167             }
168             jar.close();
169 
170             /* find the top package */
171             String[] groupIdSections = null;
172             for ( Iterator it = packageNames.iterator(); it.hasNext(); )
173             {
174                 String packageName = ( String ) it.next();
175 
176                 String[] packageNameSections = packageName.split( "\\" + FILE_SEPARATOR );
177                 if ( groupIdSections == null )
178                 {
179                     /* first candidate */
180                     groupIdSections = packageNameSections;
181                 }
182                 else
183                 // if ( packageNameSections.length < groupIdSections.length )
184                 {
185                     /*
186                      * find the common portion of current package and previous selected groupId
187                      */
188                     int i;
189                     for ( i = 0; ( i < packageNameSections.length ) && ( i < groupIdSections.length ); i++ )
190                     {
191                         if ( !packageNameSections[i].equals( groupIdSections[i] ) )
192                         {
193                             break;
194                         }
195                     }
196                     groupIdSections = new String[i];
197                     System.arraycopy( packageNameSections, 0, groupIdSections, 0, i );
198                 }
199             }
200 
201             if ( ( groupIdSections == null ) || ( groupIdSections.length == 0 ) )
202             {
203                 return null;
204             }
205 
206             /* only one section as id doesn't seem enough, so ignore it */
207             if ( groupIdSections.length == 1 )
208             {
209                 return null;
210             }
211 
212             StringBuffer sb = new StringBuffer();
213             for ( int i = 0; i < groupIdSections.length; i++ )
214             {
215                 sb.append( groupIdSections[i] );
216                 if ( i < groupIdSections.length - 1 )
217                 {
218                     sb.append( '.' );
219                 }
220             }
221             return sb.toString();
222         }
223         catch ( IOException e )
224         {
225             /* we took all the precautions to avoid this */
226             throw new RuntimeException( e );
227         }
228     }
229 
230 
231     public String getBundleFileName( Artifact artifact )
232     {
233         return getBundleSymbolicName( artifact ) + "_" + getVersion( artifact.getVersion() ) + ".jar";
234     }
235 
236 
237     public String getVersion( Artifact artifact )
238     {
239         return getVersion( artifact.getVersion() );
240     }
241 
242 
243     public String getVersion( String version )
244     {
245         return cleanupVersion( version );
246     }
247 
248     /**
249      * Clean up version parameters. Other builders use more fuzzy definitions of
250      * the version syntax. This method cleans up such a version to match an OSGi
251      * version.
252      *
253      * @param VERSION_STRING
254      * @return
255      */
256     static final Pattern FUZZY_VERSION = Pattern.compile( "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
257         Pattern.DOTALL );
258 
259 
260     static public String cleanupVersion( String version )
261     {
262         StringBuffer result = new StringBuffer();
263         Matcher m = FUZZY_VERSION.matcher( version );
264         if ( m.matches() )
265         {
266             String major = m.group( 1 );
267             String minor = m.group( 3 );
268             String micro = m.group( 5 );
269             String qualifier = m.group( 7 );
270 
271             if ( major != null )
272             {
273                 result.append( major );
274                 if ( minor != null )
275                 {
276                     result.append( "." );
277                     result.append( minor );
278                     if ( micro != null )
279                     {
280                         result.append( "." );
281                         result.append( micro );
282                         if ( qualifier != null )
283                         {
284                             result.append( "." );
285                             cleanupModifier( result, qualifier );
286                         }
287                     }
288                     else if ( qualifier != null )
289                     {
290                         result.append( ".0." );
291                         cleanupModifier( result, qualifier );
292                     }
293                     else
294                     {
295                         result.append( ".0" );
296                     }
297                 }
298                 else if ( qualifier != null )
299                 {
300                     result.append( ".0.0." );
301                     cleanupModifier( result, qualifier );
302                 }
303                 else
304                 {
305                     result.append( ".0.0" );
306                 }
307             }
308         }
309         else
310         {
311             result.append( "0.0.0." );
312             cleanupModifier( result, version );
313         }
314         return result.toString();
315     }
316 
317 
318     static void cleanupModifier( StringBuffer result, String modifier )
319     {
320         for ( int i = 0; i < modifier.length(); i++ )
321         {
322             char c = modifier.charAt( i );
323             if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_'
324                 || c == '-' )
325                 result.append( c );
326             else
327                 result.append( '_' );
328         }
329     }
330 
331 }