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 }