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