    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.util.Collection;
    24  import java.util.Iterator;
    25  import java.util.LinkedHashSet;
    26
    27  import org.apache.maven.artifact.Artifact;
    28  import org.apache.maven.plugin.MojoExecutionException;
    29  import org.apache.maven.plugin.logging.Log;
    30  import org.codehaus.plexus.util.StringUtils;
    31
    32  import aQute.bnd.osgi.Analyzer;
    33
    34
    35  /**
    36   * Add BND directives to embed selected dependencies inside a bundle
    37   *
    38   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
    39   */
    40  public final class DependencyEmbedder extends AbstractDependencyFilter
    41  {
    42      public static final String EMBED_DEPENDENCY = "Embed-Dependency";
    43      public static final String EMBED_DIRECTORY = "Embed-Directory";
    44      public static final String EMBED_STRIP_GROUP = "Embed-StripGroup";
    45      public static final String EMBED_STRIP_VERSION = "Embed-StripVersion";
    46      public static final String EMBED_TRANSITIVE = "Embed-Transitive";
    47
    48      public static final String EMBEDDED_ARTIFACTS = "Embedded-Artifacts";
    49
    50      private static final String MAVEN_DEPENDENCIES = "{maven-dependencies}";
    51
    52      private String m_embedDirectory;
    53      private String m_embedStripGroup;
    54      private String m_embedStripVersion;
    55
    56      /**
    57       * Maven logger.
    58       */
    59      private final Log m_log;
    60
    61      /**
    62       * Inlined paths.
    63       */
    64      private final Collection m_inlinedPaths;
    65
    66      /**
    67       * Embedded artifacts.
    68       */
    69      private final Collection m_embeddedArtifacts;
    70
    71
    72      public DependencyEmbedder( Log log, Collection dependencyArtifacts )
    73      {
    74          super( dependencyArtifacts );
    75
    76          m_log = log;
    77
    78          m_inlinedPaths = new LinkedHashSet();
    79          m_embeddedArtifacts = new LinkedHashSet();
    80      }
    81
    82
    83      public void processHeaders( Analyzer analyzer ) throws MojoExecutionException
    84      {
    85          StringBuffer includeResource = new StringBuffer();
    86          StringBuffer bundleClassPath = new StringBuffer();
    87          StringBuffer embeddedArtifacts = new StringBuffer();
    88
    89          m_inlinedPaths.clear();
    90          m_embeddedArtifacts.clear();
    91
    92          String embedDependencyHeader = analyzer.getProperty( EMBED_DEPENDENCY );
    93          if ( StringUtils.isNotEmpty( embedDependencyHeader ) )
    94          {
    95              m_embedDirectory = analyzer.getProperty( EMBED_DIRECTORY );
    96              m_embedStripGroup = analyzer.getProperty( EMBED_STRIP_GROUP, "true" );
    97              m_embedStripVersion = analyzer.getProperty( EMBED_STRIP_VERSION );
    98
    99              processInstructions( embedDependencyHeader );
    100
    101             for ( Iterator i = m_inlinedPaths.iterator(); i.hasNext(); )
    102             {
    103                 inlineDependency( ( String ) i.next(), includeResource );
    104             }
    105             for ( Iterator i = m_embeddedArtifacts.iterator(); i.hasNext(); )
    106             {
    107                 embedDependency( ( Artifact ) i.next(), includeResource, bundleClassPath, embeddedArtifacts );
    108             }
    109         }
    110
    111         if ( analyzer.getProperty( Analyzer.WAB ) == null && bundleClassPath.length() > 0 )
    112         {
    113             // set explicit default before merging dependency classpath
    114             if ( analyzer.getProperty( Analyzer.BUNDLE_CLASSPATH ) == null )
    115             {
    116                 analyzer.setProperty( Analyzer.BUNDLE_CLASSPATH, "." );
    117             }
    118         }
    119
    120         appendDependencies( analyzer, Analyzer.INCLUDE_RESOURCE, includeResource.toString() );
    121         appendDependencies( analyzer, Analyzer.BUNDLE_CLASSPATH, bundleClassPath.toString() );
    122         appendDependencies( analyzer, EMBEDDED_ARTIFACTS, embeddedArtifacts.toString() );
    123     }
    124
    125
    126     @Override
    127     protected void processDependencies( Collection dependencies, String inline )
    128     {
    129         if ( null == inline || "false".equalsIgnoreCase( inline ) )
    130         {
    131             m_embeddedArtifacts.addAll( dependencies );
    132         }
    133         else
    134         {
    135             for ( Iterator i = dependencies.iterator(); i.hasNext(); )
    136             {
    137                 addInlinedPaths( ( Artifact ) i.next(), inline, m_inlinedPaths );
    138             }
    139         }
    140     }
    141
    142
    143     private static void addInlinedPaths( Artifact dependency, String inline, Collection inlinedPaths )
    144     {
    145         File path = dependency.getFile();
    146         if ( null != path && path.exists() )
    147         {
    148             if ( "true".equalsIgnoreCase( inline ) || inline.length() == 0 )
    149             {
    150                 inlinedPaths.add( path.getPath() );
    151             }
    152             else
    153             {
    154                 String[] filters = inline.split( "\\|" );
    155                 for ( int i = 0; i < filters.length; i++ )
    156                 {
    157                     if ( filters[i].length() > 0 )
    158                     {
    159                         inlinedPaths.add( path + "!/" + filters[i] );
    160                     }
    161                 }
    162             }
    163         }
    164     }
    165
    166
    167     private void embedDependency( Artifact dependency, StringBuffer includeResource, StringBuffer bundleClassPath,
    168         StringBuffer embeddedArtifacts )
    169     {
    170         File sourceFile = dependency.getFile();
    171         if ( null != sourceFile && sourceFile.exists() )
    172         {
    173             String embedDirectory = m_embedDirectory;
    174             if ( "".equals( embedDirectory ) || ".".equals( embedDirectory ) )
    175             {
    176                 embedDirectory = null;
    177             }
    178
    179             if ( false == Boolean.valueOf( m_embedStripGroup ).booleanValue() )
    180             {
    181                 embedDirectory = new File( embedDirectory, dependency.getGroupId() ).getPath();
    182             }
    183
    184             StringBuffer targetFileName = new StringBuffer();
    185             targetFileName.append( dependency.getArtifactId() );
    186             if ( false == Boolean.valueOf( m_embedStripVersion ).booleanValue() )
    187             {
    188                 targetFileName.append( '-' ).append( dependency.getVersion() );
    189                 if ( StringUtils.isNotEmpty( dependency.getClassifier() ) )
    190                 {
    191                     targetFileName.append( '-' ).append( dependency.getClassifier() );
    192                 }
    193             }
    194             String extension = dependency.getArtifactHandler().getExtension();
    195             if ( StringUtils.isNotEmpty( extension ) )
    196             {
    197                 targetFileName.append( '.' ).append( extension );
    198             }
    199
    200             File targetFile = new File( embedDirectory, targetFileName.toString() );
    201
    202             String targetFilePath = targetFile.getPath();
    203
    204             // replace windows backslash with a slash
    205             if ( File.separatorChar != '/' )
    206             {
    207                 targetFilePath = targetFilePath.replace( File.separatorChar, '/' );
    208             }
    209
    210             if ( includeResource.length() > 0 )
    211             {
    212                 includeResource.append( ',' );
    213             }
    214
    215             includeResource.append( targetFilePath );
    216             includeResource.append( '=' );
    217             includeResource.append( sourceFile );
    218
    219             if ( bundleClassPath.length() > 0 )
    220             {
    221                 bundleClassPath.append( ',' );
    222             }
    223
    224             bundleClassPath.append( targetFilePath );
    225
    226             if ( embeddedArtifacts.length() > 0 )
    227             {
    228                 embeddedArtifacts.append( ',' );
    229             }
    230
    231             embeddedArtifacts.append( targetFilePath ).append( ';' );
    232             embeddedArtifacts.append( "g=\"" ).append( dependency.getGroupId() ).append( '"' );
    233             embeddedArtifacts.append( ";a=\"" ).append( dependency.getArtifactId() ).append( '"' );
    234             embeddedArtifacts.append( ";v=\"" ).append( dependency.getBaseVersion() ).append( '"' );
    235             if ( StringUtils.isNotEmpty( dependency.getClassifier() ) )
    236             {
    237                 embeddedArtifacts.append( ";c=\"" ).append( dependency.getClassifier() ).append( '"' );
    238             }
    239         }
    240     }
    241
    242
    243     private static void inlineDependency( String path, StringBuffer includeResource )
    244     {
    245         if ( includeResource.length() > 0 )
    246         {
    247             includeResource.append( ',' );
    248         }
    249
    250         includeResource.append( '@' );
    251         includeResource.append( path );
    252     }
    253
    254
    255     public Collection getInlinedPaths()
    256     {
    257         return m_inlinedPaths;
    258     }
    259
    260
    261     public Collection getEmbeddedArtifacts()
    262     {
    263         return m_embeddedArtifacts;
    264     }
    265
    266
    267     private static void appendDependencies( Analyzer analyzer, String directiveName, String mavenDependencies )
    268     {
    269         /*
    270          * similar algorithm to {maven-resources} but default behaviour here is to append rather than override
    271          */
    272         final String instruction = analyzer.getProperty( directiveName );
    273         if ( StringUtils.isNotEmpty( instruction ) )
    274         {
    275             if ( instruction.indexOf( MAVEN_DEPENDENCIES ) >= 0 )
    276             {
    277                 // if there are no embeddded dependencies, we do a special treatment and replace
    278                 // every occurance of MAVEN_DEPENDENCIES and a following comma with an empty string
    279                 if ( mavenDependencies.length() == 0 )
    280                 {
    281                     String cleanInstruction = BundlePlugin.removeTagFromInstruction( instruction, MAVEN_DEPENDENCIES );
    282                     analyzer.setProperty( directiveName, cleanInstruction );
    283                 }
    284                 else
    285                 {
    286                     String mergedInstruction = StringUtils.replace( instruction, MAVEN_DEPENDENCIES, mavenDependencies );
    287                     analyzer.setProperty( directiveName, mergedInstruction );
    288                 }
    289             }
    290             else if ( mavenDependencies.length() > 0 )
    291             {
    292                 if ( Analyzer.INCLUDE_RESOURCE.equalsIgnoreCase( directiveName ) )
    293                 {
    294                     // dependencies should be prepended so they can be overwritten by local resources
    295                     analyzer.setProperty( directiveName, mavenDependencies + ',' + instruction );
    296                 }
    297                 else
    298                 // Analyzer.BUNDLE_CLASSPATH
    299                 {
    300                     // for the classpath we want dependencies to be appended after local entries
    301                     analyzer.setProperty( directiveName, instruction + ',' + mavenDependencies );
    302                 }
    303             }
    304             // otherwise leave instruction unchanged
    305         }
    306         else if ( mavenDependencies.length() > 0 )
    307         {
    308             analyzer.setProperty( directiveName, mavenDependencies );
    309         }
    310         // otherwise leave instruction unchanged
    311     }
    312 }