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.util.Collection;
23  import java.util.Iterator;
24  import java.util.LinkedHashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.regex.Pattern;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
31  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
32  import org.apache.maven.plugin.MojoExecutionException;
33  
34  import aQute.bnd.header.Attrs;
35  import aQute.bnd.header.OSGiHeader;
36  import aQute.bnd.osgi.Instruction;
37  
38  
39  /**
40   * Apply clause-based filter over given dependencies
41   *
42   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
43   */
44  public abstract class AbstractDependencyFilter
45  {
46      private static final Pattern MISSING_KEY_PATTERN = Pattern.compile( "(^|,)\\p{Blank}*(!)?\\p{Blank}*([a-zA-Z]+=)" );
47      private static final String PLACEHOLDER = "$$PLACEHOLDER$$";
48  
49      /**
50       * Dependency artifacts.
51       */
52      private final Collection<Artifact> m_dependencyArtifacts;
53  
54  
55      public AbstractDependencyFilter( Collection<Artifact> dependencyArtifacts )
56      {
57          m_dependencyArtifacts = dependencyArtifacts;
58      }
59  
60      private static abstract class DependencyFilter implements ArtifactFilter
61      {
62          private final Instruction m_instruction;
63          private final String m_defaultValue;
64  
65  
66          public DependencyFilter( String expression )
67          {
68              this( expression, "" );
69          }
70  
71  
72          public DependencyFilter( String expression, String defaultValue )
73          {
74              m_instruction = new Instruction( expression );
75              m_defaultValue = defaultValue;
76          }
77  
78          public abstract boolean include( Artifact dependency );
79  
80          boolean matches( String text )
81          {
82              boolean result;
83  
84              if ( null == text )
85              {
86                  result = m_instruction.matches( m_defaultValue );
87              }
88              else
89              {
90                  result = m_instruction.matches( text );
91              }
92  
93              return m_instruction.isNegated() ? !result : result;
94          }
95      }
96  
97      protected final void processInstructions( String header ) throws MojoExecutionException
98      {
99          Map<String,Attrs> instructions = OSGiHeader.parseHeader( MISSING_KEY_PATTERN.matcher( header ).replaceAll( "$1$2*;$3" ) );
100 
101         Collection<Artifact> availableDependencies = new LinkedHashSet<Artifact>( m_dependencyArtifacts );
102 
103         for ( Iterator<Map.Entry<String,Attrs>> clauseIterator = instructions.entrySet().iterator(); clauseIterator.hasNext(); )
104         {
105             String inline = "false";
106 
107             // always start with a fresh *modifiable* collection for each unique clause
108             Collection<Artifact> filteredDependencies = new LinkedHashSet<Artifact>( availableDependencies );
109 
110             // CLAUSE: REGEXP --> { ATTRIBUTE MAP }
111             Map.Entry<String,Attrs> clause = clauseIterator.next();
112             String primaryKey = clause.getKey().replaceFirst( "~+$", "" );
113             boolean isNegative = primaryKey.startsWith( "!" );
114             if ( isNegative )
115             {
116                 primaryKey = primaryKey.substring( 1 );
117             }
118 
119             final AndArtifactFilter andArtifactFilter = new AndArtifactFilter();
120             if ( !"*".equals( primaryKey ) )
121             {
122                 ArtifactFilter filter = new DependencyFilter( primaryKey )
123                 {
124                     @Override
125                     public boolean include( Artifact dependency )
126                     {
127                         return super.matches( dependency.getArtifactId() );
128                     }
129                 };
130                 // FILTER ON MAIN CLAUSE
131                 andArtifactFilter.add(filter);
132             }
133 
134             for ( Iterator<Map.Entry<String,String>> attrIterator = clause.getValue().entrySet().iterator(); attrIterator.hasNext(); )
135             {
136                 final ArtifactFilter filter;
137                 // ATTRIBUTE: KEY --> REGEXP
138                 Map.Entry<String,String> attr = attrIterator.next();
139                 if ( "groupId".equals( attr.getKey() ) )
140                 {
141                     filter = new DependencyFilter( attr.getValue() )
142                     {
143                         @Override
144                         public boolean include( Artifact dependency )
145                         {
146                             return super.matches( dependency.getGroupId() );
147                         }
148                     };
149                 }
150                 else if ( "artifactId".equals( attr.getKey() ) )
151                 {
152                     filter = new DependencyFilter( attr.getValue() )
153                     {
154                         @Override
155                         public boolean include( Artifact dependency )
156                         {
157                             return super.matches( dependency.getArtifactId() );
158                         }
159                     };
160                 }
161                 else if ( "version".equals( attr.getKey() ) )
162                 {
163                     filter = new DependencyFilter( attr.getValue() )
164                     {
165                         @Override
166                         public boolean include( Artifact dependency )
167                         {
168                             try
169                             {
170                                 // use the symbolic version if available (ie. 1.0.0-SNAPSHOT)
171                                 return super.matches( dependency.getSelectedVersion().toString() );
172                             }
173                             catch ( Exception e )
174                             {
175                                 return super.matches( dependency.getVersion() );
176                             }
177                         }
178                     };
179                 }
180                 else if ( "scope".equals( attr.getKey() ) )
181                 {
182                     filter = new DependencyFilter( attr.getValue(), "compile" )
183                     {
184                         @Override
185                         public boolean include( Artifact dependency )
186                         {
187                             return super.matches( dependency.getScope() );
188                         }
189                     };
190                 }
191                 else if ( "type".equals( attr.getKey() ) )
192                 {
193                     filter = new DependencyFilter( attr.getValue(), "jar" )
194                     {
195                         @Override
196                         public boolean include( Artifact dependency )
197                         {
198                             return super.matches( dependency.getType() );
199                         }
200                     };
201                 }
202                 else if ( "classifier".equals( attr.getKey() ) )
203                 {
204                     // bnd-4.0.0+ no longer accepts empty instructions. However maven expects
205                     // the classifier can be an empty value, so use a placeholder for this
206                     // scenario.
207                     filter = new DependencyFilter( ( "".equals(attr.getValue()) ) ? PLACEHOLDER : attr.getValue() )
208                     {
209                         @Override
210                         public boolean include( Artifact dependency )
211                         {
212                             return super.matches( ( ( dependency.getClassifier() == null ) || ( "".equals( dependency.getClassifier() ) ) ) ? PLACEHOLDER : dependency.getClassifier() );
213                         }
214                     };
215                 }
216                 else if ( "optional".equals( attr.getKey() ) )
217                 {
218                     filter = new DependencyFilter( attr.getValue(), "false" )
219                     {
220                         @Override
221                         public boolean include( Artifact dependency )
222                         {
223                             return super.matches( "" + dependency.isOptional() );
224                         }
225                     };
226                 }
227                 else if ( "inline".equals( attr.getKey() ) )
228                 {
229                     inline = attr.getValue();
230                     continue;
231                 }
232                 else
233                 {
234                     throw new MojoExecutionException( "Unexpected attribute " + attr.getKey() );
235                 }
236 
237                 // FILTER ON EACH ATTRIBUTE
238                 andArtifactFilter.add( filter );
239             }
240 
241             filteredDependencies( andArtifactFilter, filteredDependencies );
242 
243             if ( isNegative )
244             {
245                 // negative clauses reduce the set of available artifacts
246                 availableDependencies.removeAll( filteredDependencies );
247                 if ( !clauseIterator.hasNext() )
248                 {
249                     // assume there's an implicit * missing at the end
250                     processDependencies( availableDependencies, inline );
251                 }
252             }
253             else
254             {
255                 // positive clause; doesn't alter the available artifacts
256                 processDependencies( filteredDependencies, inline );
257             }
258         }
259     }
260 
261 
262     protected abstract void processDependencies( Collection<Artifact> dependencies, String inline );
263 
264     private void filteredDependencies( final ArtifactFilter artifactFilter, Collection<Artifact> filteredDependencies )
265     {
266         filteredDependencies.removeIf( artifact -> !artifactFilter.include( artifact ) );
267     }
268 }