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