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.maven.internal.impl.resolver.relocation;
20  
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.function.Predicate;
25  import java.util.stream.Collectors;
26  import java.util.stream.Stream;
27  
28  import org.apache.maven.api.di.Named;
29  import org.apache.maven.api.di.Priority;
30  import org.apache.maven.api.di.Singleton;
31  import org.apache.maven.api.model.Model;
32  import org.apache.maven.internal.impl.resolver.MavenArtifactRelocationSource;
33  import org.apache.maven.internal.impl.resolver.RelocatedArtifact;
34  import org.eclipse.aether.RepositorySystemSession;
35  import org.eclipse.aether.artifact.Artifact;
36  import org.eclipse.aether.artifact.DefaultArtifact;
37  import org.eclipse.aether.resolution.ArtifactDescriptorException;
38  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * Relocation source from user properties.
44   *
45   * @since 4.0.0
46   */
47  @Singleton
48  @Named(UserPropertiesArtifactRelocationSource.NAME)
49  @Priority(50)
50  @SuppressWarnings("checkstyle:MagicNumber")
51  public final class UserPropertiesArtifactRelocationSource implements MavenArtifactRelocationSource {
52      public static final String NAME = "userProperties";
53      private static final Logger LOGGER = LoggerFactory.getLogger(UserPropertiesArtifactRelocationSource.class);
54  
55      private static final String CONFIG_PROP_RELOCATIONS_ENTRIES = "maven.relocations.entries";
56  
57      private static final Artifact SENTINEL = new DefaultArtifact("org.apache.maven.banned:user-relocation:1.0");
58  
59      @Override
60      public Artifact relocatedTarget(
61              RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model)
62              throws ArtifactDescriptorException {
63          Relocations relocations = (Relocations) session.getData()
64                  .computeIfAbsent(getClass().getName() + ".relocations", () -> parseRelocations(session));
65          if (relocations != null) {
66              Artifact original = artifactDescriptorResult.getRequest().getArtifact();
67              Relocation relocation = relocations.getRelocation(original);
68              if (relocation != null
69                      && (isProjectContext(artifactDescriptorResult.getRequest().getRequestContext())
70                              || relocation.global)) {
71                  if (relocation.target == SENTINEL) {
72                      String message = "The artifact " + original + " has been banned from resolution: "
73                              + (relocation.global ? "User global ban" : "User project ban");
74                      LOGGER.debug(message);
75                      throw new ArtifactDescriptorException(artifactDescriptorResult, message);
76                  }
77                  Artifact result = new RelocatedArtifact(
78                          original,
79                          isAny(relocation.target.getGroupId()) ? null : relocation.target.getGroupId(),
80                          isAny(relocation.target.getArtifactId()) ? null : relocation.target.getArtifactId(),
81                          isAny(relocation.target.getClassifier()) ? null : relocation.target.getClassifier(),
82                          isAny(relocation.target.getExtension()) ? null : relocation.target.getExtension(),
83                          isAny(relocation.target.getVersion()) ? null : relocation.target.getVersion(),
84                          relocation.global ? "User global relocation" : "User project relocation");
85                  LOGGER.debug(
86                          "The artifact {} has been relocated to {}: {}",
87                          original,
88                          result,
89                          relocation.global ? "User global relocation" : "User project relocation");
90                  return result;
91              }
92          }
93          return null;
94      }
95  
96      private boolean isProjectContext(String context) {
97          return context != null && context.startsWith("project");
98      }
99  
100     private static boolean isAny(String str) {
101         return "*".equals(str);
102     }
103 
104     private static boolean matches(String pattern, String str) {
105         if (isAny(pattern)) {
106             return true;
107         } else if (pattern.endsWith("*")) {
108             return str.startsWith(pattern.substring(0, pattern.length() - 1));
109         } else {
110             return Objects.equals(pattern, str);
111         }
112     }
113 
114     private static Predicate<Artifact> artifactPredicate(Artifact artifact) {
115         return a -> matches(artifact.getGroupId(), a.getGroupId())
116                 && matches(artifact.getArtifactId(), a.getArtifactId())
117                 && matches(artifact.getBaseVersion(), a.getBaseVersion())
118                 && matches(artifact.getExtension(), a.getExtension())
119                 && matches(artifact.getClassifier(), a.getClassifier());
120     }
121 
122     private static class Relocation {
123         private final Predicate<Artifact> predicate;
124         private final boolean global;
125         private final Artifact source;
126         private final Artifact target;
127 
128         private Relocation(boolean global, Artifact source, Artifact target) {
129             this.predicate = artifactPredicate(source);
130             this.global = global;
131             this.source = source;
132             this.target = target;
133         }
134 
135         @Override
136         public String toString() {
137             return source + (global ? " >> " : " > ") + target;
138         }
139     }
140 
141     private static class Relocations {
142         private final List<Relocation> relocations;
143 
144         private Relocations(List<Relocation> relocations) {
145             this.relocations = relocations;
146         }
147 
148         private Relocation getRelocation(Artifact artifact) {
149             return relocations.stream()
150                     .filter(r -> r.predicate.test(artifact))
151                     .findFirst()
152                     .orElse(null);
153         }
154     }
155 
156     private Relocations parseRelocations(RepositorySystemSession session) {
157         String relocationsEntries = (String) session.getConfigProperties().get(CONFIG_PROP_RELOCATIONS_ENTRIES);
158         if (relocationsEntries == null) {
159             return null;
160         }
161         String[] entries = relocationsEntries.split(",");
162         try (Stream<String> lines = Arrays.stream(entries)) {
163             List<Relocation> relocationList = lines.filter(
164                             l -> l != null && !l.trim().isEmpty())
165                     .map(l -> {
166                         boolean global;
167                         String splitExpr;
168                         if (l.contains(">>")) {
169                             global = true;
170                             splitExpr = ">>";
171                         } else if (l.contains(">")) {
172                             global = false;
173                             splitExpr = ">";
174                         } else {
175                             throw new IllegalArgumentException("Unrecognized entry: " + l);
176                         }
177                         String[] parts = l.split(splitExpr);
178                         if (parts.length < 1) {
179                             throw new IllegalArgumentException("Unrecognized entry: " + l);
180                         }
181                         Artifact s = parseArtifact(parts[0]);
182                         Artifact t;
183                         if (parts.length > 1) {
184                             t = parseArtifact(parts[1]);
185                         } else {
186                             t = SENTINEL;
187                         }
188                         return new Relocation(global, s, t);
189                     })
190                     .collect(Collectors.toList());
191             LOGGER.info("Parsed {} user relocations", relocationList.size());
192             return new Relocations(relocationList);
193         }
194     }
195 
196     private static Artifact parseArtifact(String coord) {
197         Artifact s;
198         String[] parts = coord.split(":");
199         switch (parts.length) {
200             case 3:
201                 s = new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]);
202                 break;
203             case 4:
204                 s = new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]);
205                 break;
206             case 5:
207                 s = new DefaultArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]);
208                 break;
209             default:
210                 throw new IllegalArgumentException("Bad artifact coordinates " + coord
211                         + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
212         }
213         return s;
214     }
215 }