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