    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.felix.bundleplugin;
    20
    21
    22  import java.io.File;
    23  import java.io.FileNotFoundException;
    24  import java.io.FileOutputStream;
    25  import java.io.IOException;
    26  import java.io.OutputStream;
    27  import java.util.Iterator;
    28  import java.util.LinkedHashMap;
    29  import java.util.Map;
    30  import java.util.Map.Entry;
    31  import java.util.Properties;
    32  import java.util.jar.Manifest;
    33
    34  import org.apache.maven.plugin.MojoExecutionException;
    35  import org.apache.maven.plugin.MojoFailureException;
    36  import org.apache.maven.project.MavenProject;
    37
    38  import aQute.bnd.osgi.Analyzer;
    39  import aQute.bnd.osgi.Builder;
    40  import aQute.bnd.osgi.Jar;
    41  import aQute.bnd.osgi.Resource;
    42
    43
    44  /**
    45   * Generate an OSGi manifest for this project
    46   *
    47   * @goal manifest
    48   * @phase process-classes
    49   * @requiresDependencyResolution test
    50   * @threadSafe
    51   */
    52  public class ManifestPlugin extends BundlePlugin
    53  {
    54      /**
    55       * When true, generate the manifest by rebuilding the full bundle in memory
    56       *
    57       * @parameter expression="${rebuildBundle}"
    58       */
    59      protected boolean rebuildBundle;
    60
    61
    62      @Override
    63      protected void execute( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    64          throws MojoExecutionException
    65      {
    66          Manifest manifest;
    67          try
    68          {
    69              manifest = getManifest( project, instructions, properties, classpath );
    70          }
    71          catch ( FileNotFoundException e )
    72          {
    73              throw new MojoExecutionException( "Cannot find " + e.getMessage()
    74                  + " (manifest goal must be run after compile phase)", e );
    75          }
    76          catch ( IOException e )
    77          {
    78              throw new MojoExecutionException( "Error trying to generate Manifest", e );
    79          }
    80          catch ( MojoFailureException e )
    81          {
    82              getLog().error( e.getLocalizedMessage() );
    83              throw new MojoExecutionException( "Error(s) found in manifest configuration", e );
    84          }
    85          catch ( Exception e )
    86          {
    87              getLog().error( "An internal error occurred", e );
    88              throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
    89          }
    90
    91          File outputFile = new File( manifestLocation, "MANIFEST.MF" );
    92
    93          try
    94          {
    95              writeManifest( manifest, outputFile );
    96          }
    97          catch ( IOException e )
    98          {
    99              throw new MojoExecutionException( "Error trying to write Manifest to file " + outputFile, e );
    100         }
    101     }
    102
    103
    104     public Manifest getManifest( MavenProject project, Jar[] classpath ) throws IOException, MojoFailureException,
    105         MojoExecutionException, Exception
    106     {
    107         return getManifest( project, new LinkedHashMap(), new Properties(), classpath );
    108     }
    109
    110
    111     public Manifest getManifest( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    112         throws IOException, MojoFailureException, MojoExecutionException, Exception
    113     {
    114         Analyzer analyzer = getAnalyzer( project, instructions, properties, classpath );
    115         boolean hasErrors = reportErrors( "Manifest " + project.getArtifact(), analyzer );
    116         if ( hasErrors )
    117         {
    118             String failok = analyzer.getProperty( "-failok" );
    119             if ( null == failok || "false".equalsIgnoreCase( failok ) )
    120             {
    121                 throw new MojoFailureException( "Error(s) found in manifest configuration" );
    122             }
    123         }
    124
    125         Jar jar = analyzer.getJar();
    126
    127         if ( unpackBundle )
    128         {
    129             File outputFile = getOutputDirectory();
    130             for ( Entry<String, Resource> entry : jar.getResources().entrySet() )
    131             {
    132                 File entryFile = new File( outputFile, entry.getKey() );
    133                 if ( !entryFile.exists() || entry.getValue().lastModified() == 0 )
    134                 {
    135                     entryFile.getParentFile().mkdirs();
    136                     OutputStream os = new FileOutputStream( entryFile );
    137                     entry.getValue().write( os );
    138                     os.close();
    139                 }
    140             }
    141         }
    142
    143         Manifest manifest = jar.getManifest();
    144
    145         // cleanup...
    146         analyzer.close();
    147
    148         return manifest;
    149     }
    150
    151
    152     protected Analyzer getAnalyzer( MavenProject project, Jar[] classpath ) throws IOException, MojoExecutionException,
    153         Exception
    154     {
    155         return getAnalyzer( project, new LinkedHashMap(), new Properties(), classpath );
    156     }
    157
    158
    159     protected Analyzer getAnalyzer( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    160         throws IOException, MojoExecutionException, Exception
    161     {
    162         if ( rebuildBundle && supportedProjectTypes.contains( project.getArtifact().getType() ) )
    163         {
    164             return buildOSGiBundle( project, instructions, properties, classpath );
    165         }
    166
    167         File file = getOutputDirectory();
    168         if ( file == null )
    169         {
    170             file = project.getArtifact().getFile();
    171         }
    172
    173         if ( !file.exists() )
    174         {
    175             if ( file.equals( getOutputDirectory() ) )
    176             {
    177                 file.mkdirs();
    178             }
    179             else
    180             {
    181                 throw new FileNotFoundException( file.getPath() );
    182             }
    183         }
    184
    185         Builder analyzer = getOSGiBuilder( project, instructions, properties, classpath );
    186
    187         analyzer.setJar( file );
    188
    189         // calculateExportsFromContents when we have no explicit instructions defining
    190         // the contents of the bundle *and* we are not analyzing the output directory,
    191         // otherwise fall-back to addMavenInstructions approach
    192
    193         boolean isOutputDirectory = file.equals( getOutputDirectory() );
    194
    195         if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null
    196             && analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null
    197             && analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) == null && !isOutputDirectory )
    198         {
    199             String export = calculateExportsFromContents( analyzer.getJar() );
    200             analyzer.setProperty( Analyzer.EXPORT_PACKAGE, export );
    201         }
    202
    203         addMavenInstructions( project, analyzer );
    204
    205         // if we spot Embed-Dependency and the bundle is "target/classes", assume we need to rebuild
    206         if ( analyzer.getProperty( DependencyEmbedder.EMBED_DEPENDENCY ) != null && isOutputDirectory )
    207         {
    208             analyzer.build();
    209         }
    210         else
    211         {
    212             analyzer.mergeManifest( analyzer.getJar().getManifest() );
    213             analyzer.getJar().setManifest( analyzer.calcManifest() );
    214         }
    215
    216         mergeMavenManifest( project, analyzer );
    217
    218         return analyzer;
    219     }
    220
    221
    222     public static void writeManifest( Manifest manifest, File outputFile ) throws IOException
    223     {
    224         outputFile.getParentFile().mkdirs();
    225
    226         FileOutputStream os;
    227         os = new FileOutputStream( outputFile );
    228         try
    229         {
    230             Jar.writeManifest( manifest, os );
    231         }
    232         finally
    233         {
    234             try
    235             {
    236                 os.close();
    237             }
    238             catch ( IOException e )
    239             {
    240                 // nothing we can do here
    241             }
    242         }
    243     }
    244
    245
    246     /*
    247      * Patched version of bnd's Analyzer.calculateExportsFromContents
    248      */
    249     public static String calculateExportsFromContents( Jar bundle )
    250     {
    251         String ddel = "";
    252         StringBuffer sb = new StringBuffer();
    253         Map<String, Map<String, Resource>> map = bundle.getDirectories();
    254         for ( Iterator<Entry<String, Map<String, Resource>>> i = map.entrySet().iterator(); i.hasNext(); )
    255         {
    256             //----------------------------------------------------
    257             // should also ignore directories with no resources
    258             //----------------------------------------------------
    259             Entry<String, Map<String, Resource>> entry = i.next();
    260             if ( entry.getValue() == null || entry.getValue().isEmpty() )
    261                 continue;
    262             //----------------------------------------------------
    263             String directory = entry.getKey();
    264             if ( directory.equals( "META-INF" ) || directory.startsWith( "META-INF/" ) )
    265                 continue;
    266             if ( directory.equals( "OSGI-OPT" ) || directory.startsWith( "OSGI-OPT/" ) )
    267                 continue;
    268             if ( directory.equals( "/" ) )
    269                 continue;
    270
    271             if ( directory.endsWith( "/" ) )
    272                 directory = directory.substring( 0, directory.length() - 1 );
    273
    274             directory = directory.replace( '/', '.' );
    275             sb.append( ddel );
    276             sb.append( directory );
    277             ddel = ",";
    278         }
    279         return sb.toString();
    280     }
    281 }