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.transformation;
20  
21  import javax.annotation.PreDestroy;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.nio.file.StandardCopyOption;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.Set;
34  import java.util.concurrent.CopyOnWriteArraySet;
35  import java.util.function.BiConsumer;
36  
37  import org.apache.maven.feature.Features;
38  import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
39  import org.apache.maven.model.building.TransformerContext;
40  import org.apache.maven.model.transform.RawToConsumerPomXMLFilterFactory;
41  import org.apache.maven.model.transform.pull.XmlUtils;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.project.artifact.ProjectArtifact;
44  import org.codehaus.plexus.util.ReaderFactory;
45  import org.codehaus.plexus.util.xml.XmlStreamReader;
46  import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
47  import org.codehaus.plexus.util.xml.pull.MXParser;
48  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
49  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
50  import org.eclipse.aether.RepositorySystemSession;
51  import org.eclipse.aether.artifact.Artifact;
52  import org.eclipse.aether.artifact.DefaultArtifact;
53  import org.eclipse.aether.deployment.DeployRequest;
54  import org.eclipse.aether.installation.InstallRequest;
55  
56  /**
57   * Consumer POM transformer.
58   *
59   * @since TBD
60   */
61  @Singleton
62  @Named("consumer-pom")
63  public final class ConsumerPomArtifactTransformer {
64  
65      private static final String CONSUMER_POM_CLASSIFIER = "consumer";
66  
67      private final Set<Path> toDelete = new CopyOnWriteArraySet<>();
68  
69      public void injectTransformedArtifacts(MavenProject project, RepositorySystemSession session) throws IOException {
70          if (project.getFile() == null) {
71              // If there is no build POM there is no reason to inject artifacts for the consumer POM.
72              return;
73          }
74          if (isActive(session)) {
75              Path generatedFile;
76              String buildDirectory =
77                      project.getBuild() != null ? project.getBuild().getDirectory() : null;
78              if (buildDirectory == null) {
79                  generatedFile = Files.createTempFile(CONSUMER_POM_CLASSIFIER, "pom");
80              } else {
81                  Path buildDir = Paths.get(buildDirectory);
82                  Files.createDirectories(buildDir);
83                  generatedFile = Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER, "pom");
84              }
85              deferDeleteFile(generatedFile);
86              project.addAttachedArtifact(new ConsumerPomArtifact(project, generatedFile, session));
87          } else if (project.getModel().isRoot()) {
88              throw new IllegalStateException(
89                      "The use of the root attribute on the model requires the buildconsumer feature to be active");
90          }
91      }
92  
93      private void deferDeleteFile(Path generatedFile) {
94          toDelete.add(generatedFile.toAbsolutePath());
95      }
96  
97      @PreDestroy
98      private void doDeleteFiles() {
99          for (Path file : toDelete) {
100             try {
101                 Files.delete(file);
102             } catch (IOException e) {
103                 // ignore, we did our best...
104             }
105         }
106     }
107 
108     public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) {
109         if (isActive(session) && consumerPomPresent(request.getArtifacts())) {
110             request.setArtifacts(replacePom(request.getArtifacts()));
111         }
112         return request;
113     }
114 
115     public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) {
116         if (isActive(session) && consumerPomPresent(request.getArtifacts())) {
117             request.setArtifacts(replacePom(request.getArtifacts()));
118         }
119         return request;
120     }
121 
122     private boolean isActive(RepositorySystemSession session) {
123         return Features.buildConsumer(session.getUserProperties()).isActive();
124     }
125 
126     private boolean consumerPomPresent(Collection<Artifact> artifacts) {
127         return artifacts.stream().anyMatch(a -> CONSUMER_POM_CLASSIFIER.equals(a.getClassifier()));
128     }
129 
130     private Collection<Artifact> replacePom(Collection<Artifact> artifacts) {
131         ArrayList<Artifact> result = new ArrayList<>(artifacts.size());
132         for (Artifact artifact : artifacts) {
133             if (CONSUMER_POM_CLASSIFIER.equals(artifact.getClassifier())) {
134                 // if under CONSUMER_POM_CLASSIFIER, move it to "" classifier
135                 DefaultArtifact remapped = new DefaultArtifact(
136                         artifact.getGroupId(),
137                         artifact.getArtifactId(),
138                         "",
139                         artifact.getExtension(),
140                         artifact.getVersion(),
141                         artifact.getProperties(),
142                         artifact.getFile());
143                 result.add(remapped);
144             } else if ("".equals(artifact.getClassifier())
145                             && (artifact.getExtension().equals("pom"))
146                     || artifact.getExtension().startsWith("pom.")) {
147                 // skip POM and POM subordinates
148                 continue;
149             } else {
150                 // everything else: add as is
151                 result.add(artifact);
152             }
153         }
154         return result;
155     }
156 
157     /**
158      * Consumer POM is transformed from original POM.
159      */
160     private static class ConsumerPomArtifact extends TransformedArtifact {
161 
162         private ConsumerPomArtifact(MavenProject mavenProject, Path target, RepositorySystemSession session) {
163             super(
164                     new ProjectArtifact(mavenProject),
165                     () -> mavenProject.getFile().toPath(),
166                     CONSUMER_POM_CLASSIFIER,
167                     "pom",
168                     target,
169                     transformer(session));
170         }
171 
172         private static BiConsumer<Path, Path> transformer(RepositorySystemSession session) {
173             TransformerContext context = (TransformerContext) session.getData().get(TransformerContext.KEY);
174             return (src, dest) -> {
175                 try (InputStream inputStream = transform(src, context)) {
176                     Files.createDirectories(dest.getParent());
177                     Files.copy(inputStream, dest, StandardCopyOption.REPLACE_EXISTING);
178                 } catch (XmlPullParserException | IOException e) {
179                     throw new RuntimeException(e);
180                 }
181             };
182         }
183     }
184 
185     /**
186      * The actual transformation: visible for testing.
187      */
188     static InputStream transform(Path pomFile, TransformerContext context) throws IOException, XmlPullParserException {
189         XmlStreamReader reader = ReaderFactory.newXmlReader(Files.newInputStream(pomFile));
190         XmlPullParser parser = new MXParser(EntityReplacementMap.defaultEntityReplacementMap);
191         parser.setInput(reader);
192         parser = new RawToConsumerPomXMLFilterFactory(new DefaultBuildPomXMLFilterFactory(context, true))
193                 .get(parser, pomFile);
194 
195         return XmlUtils.writeDocument(reader, parser);
196     }
197 }