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