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  import java.util.Map;
27  
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.logging.Log;
31  import org.codehaus.plexus.util.StringUtils;
32  
33  import aQute.lib.osgi.Analyzer;
34  import aQute.libg.header.OSGiHeader;
35  
36  
37  /**
38   * Add BND directives to embed selected dependencies inside a bundle
39   * 
40   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
41   */
42  public final class DependencyEmbedder extends AbstractDependencyFilter
43  {
44      public static final String EMBED_DEPENDENCY = "Embed-Dependency";
45      public static final String EMBED_DIRECTORY = "Embed-Directory";
46      public static final String EMBED_STRIP_GROUP = "Embed-StripGroup";
47      public static final String EMBED_STRIP_VERSION = "Embed-StripVersion";
48      public static final String EMBED_TRANSITIVE = "Embed-Transitive";
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  
88          m_inlinedPaths.clear();
89          m_embeddedArtifacts.clear();
90  
91          String embedDependencyHeader = analyzer.getProperty( EMBED_DEPENDENCY );
92          if ( null != embedDependencyHeader && embedDependencyHeader.length() > 0 )
93          {
94              m_embedDirectory = analyzer.getProperty( EMBED_DIRECTORY );
95              m_embedStripGroup = analyzer.getProperty( EMBED_STRIP_GROUP, "true" );
96              m_embedStripVersion = analyzer.getProperty( EMBED_STRIP_VERSION );
97  
98              Map embedInstructions = OSGiHeader.parseHeader( embedDependencyHeader );
99              processInstructions( embedInstructions );
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 );
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     }
123 
124 
125     @Override
126     protected void processDependencies( String tag, String inline, Collection dependencies )
127     {
128         if ( dependencies.isEmpty() )
129         {
130             m_log.warn( EMBED_DEPENDENCY + ": clause \"" + tag + "\" did not match any dependencies" );
131         }
132 
133         if ( null == inline || "false".equalsIgnoreCase( inline ) )
134         {
135             m_embeddedArtifacts.addAll( dependencies );
136         }
137         else
138         {
139             for ( Iterator i = dependencies.iterator(); i.hasNext(); )
140             {
141                 addInlinedPaths( ( Artifact ) i.next(), inline, m_inlinedPaths );
142             }
143         }
144     }
145 
146 
147     private static void addInlinedPaths( Artifact dependency, String inline, Collection inlinedPaths )
148     {
149         File path = dependency.getFile();
150         if ( null != path && path.exists() )
151         {
152             if ( "true".equalsIgnoreCase( inline ) || inline.length() == 0 )
153             {
154                 inlinedPaths.add( path.getPath() );
155             }
156             else
157             {
158                 String[] filters = inline.split( "\\|" );
159                 for ( int i = 0; i < filters.length; i++ )
160                 {
161                     if ( filters[i].length() > 0 )
162                     {
163                         inlinedPaths.add( path + "!/" + filters[i] );
164                     }
165                 }
166             }
167         }
168     }
169 
170 
171     private void embedDependency( Artifact dependency, StringBuffer includeResource, StringBuffer bundleClassPath )
172     {
173         File sourceFile = dependency.getFile();
174         if ( null != sourceFile && sourceFile.exists() )
175         {
176             String embedDirectory = m_embedDirectory;
177             if ( "".equals( embedDirectory ) || ".".equals( embedDirectory ) )
178             {
179                 embedDirectory = null;
180             }
181 
182             if ( false == Boolean.valueOf( m_embedStripGroup ).booleanValue() )
183             {
184                 embedDirectory = new File( embedDirectory, dependency.getGroupId() ).getPath();
185             }
186 
187             File targetFile;
188             if ( Boolean.valueOf( m_embedStripVersion ).booleanValue() )
189             {
190                 String extension = dependency.getArtifactHandler().getExtension();
191                 if ( extension != null )
192                 {
193                     targetFile = new File( embedDirectory, dependency.getArtifactId() + "." + extension );
194                 }
195                 else
196                 {
197                     targetFile = new File( embedDirectory, dependency.getArtifactId() );
198                 }
199             }
200             else
201             {
202                 targetFile = new File( embedDirectory, sourceFile.getName() );
203             }
204 
205             String targetFilePath = targetFile.getPath();
206 
207             // replace windows backslash with a slash
208             if ( File.separatorChar != '/' )
209             {
210                 targetFilePath = targetFilePath.replace( File.separatorChar, '/' );
211             }
212 
213             if ( includeResource.length() > 0 )
214             {
215                 includeResource.append( ',' );
216             }
217 
218             includeResource.append( targetFilePath );
219             includeResource.append( '=' );
220             includeResource.append( sourceFile );
221 
222             if ( bundleClassPath.length() > 0 )
223             {
224                 bundleClassPath.append( ',' );
225             }
226 
227             bundleClassPath.append( targetFilePath );
228         }
229     }
230 
231 
232     private static void inlineDependency( String path, StringBuffer includeResource )
233     {
234         if ( includeResource.length() > 0 )
235         {
236             includeResource.append( ',' );
237         }
238 
239         includeResource.append( '@' );
240         includeResource.append( path );
241     }
242 
243 
244     public Collection getInlinedPaths()
245     {
246         return m_inlinedPaths;
247     }
248 
249 
250     public Collection getEmbeddedArtifacts()
251     {
252         return m_embeddedArtifacts;
253     }
254 
255 
256     private static void appendDependencies( Analyzer analyzer, String directiveName, String mavenDependencies )
257     {
258         /*
259          * similar algorithm to {maven-resources} but default behaviour here is to append rather than override
260          */
261         final String instruction = analyzer.getProperty( directiveName );
262         if ( instruction != null && instruction.length() > 0 )
263         {
264             if ( instruction.indexOf( MAVEN_DEPENDENCIES ) >= 0 )
265             {
266                 // if there are no embeddded dependencies, we do a special treatment and replace
267                 // every occurance of MAVEN_DEPENDENCIES and a following comma with an empty string
268                 if ( mavenDependencies.length() == 0 )
269                 {
270                     String cleanInstruction = BundlePlugin.removeTagFromInstruction( instruction, MAVEN_DEPENDENCIES );
271                     analyzer.setProperty( directiveName, cleanInstruction );
272                 }
273                 else
274                 {
275                     String mergedInstruction = StringUtils.replace( instruction, MAVEN_DEPENDENCIES, mavenDependencies );
276                     analyzer.setProperty( directiveName, mergedInstruction );
277                 }
278             }
279             else if ( mavenDependencies.length() > 0 )
280             {
281                 if ( Analyzer.INCLUDE_RESOURCE.equalsIgnoreCase( directiveName ) )
282                 {
283                     // dependencies should be prepended so they can be overwritten by local resources
284                     analyzer.setProperty( directiveName, mavenDependencies + ',' + instruction );
285                 }
286                 else
287                 // Analyzer.BUNDLE_CLASSPATH
288                 {
289                     // for the classpath we want dependencies to be appended after local entries
290                     analyzer.setProperty( directiveName, instruction + ',' + mavenDependencies );
291                 }
292             }
293             // otherwise leave instruction unchanged
294         }
295         else if ( mavenDependencies.length() > 0 )
296         {
297             analyzer.setProperty( directiveName, mavenDependencies );
298         }
299         // otherwise leave instruction unchanged
300     }
301 }