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.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  import javax.xml.stream.XMLStreamException;
25  
26  import java.io.IOException;
27  import java.io.Writer;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.nio.file.Paths;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.concurrent.CopyOnWriteArraySet;
36  
37  import org.apache.maven.api.feature.Features;
38  import org.apache.maven.api.model.Model;
39  import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer;
40  import org.apache.maven.model.building.ModelBuildingException;
41  import org.apache.maven.model.v4.MavenStaxWriter;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.project.artifact.ProjectArtifact;
44  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
45  import org.eclipse.aether.RepositorySystemSession;
46  import org.eclipse.aether.artifact.Artifact;
47  import org.eclipse.aether.artifact.DefaultArtifact;
48  import org.eclipse.aether.deployment.DeployRequest;
49  import org.eclipse.aether.installation.InstallRequest;
50  import org.eclipse.sisu.PreDestroy;
51  
52  /**
53   * Consumer POM transformer.
54   *
55   * @since TBD
56   */
57  @Singleton
58  @Named("consumer-pom")
59  class DefaultConsumerPomArtifactTransformer implements ConsumerPomArtifactTransformer {
60  
61      private static final String CONSUMER_POM_CLASSIFIER = "consumer";
62  
63      private static final String BUILD_POM_CLASSIFIER = "build";
64  
65      private static final String NAMESPACE_FORMAT = "http://maven.apache.org/POM/%s";
66  
67      private static final String SCHEMA_LOCATION_FORMAT = "https://maven.apache.org/xsd/maven-%s.xsd";
68  
69      private final Set<Path> toDelete = new CopyOnWriteArraySet<>();
70  
71      private final ConsumerPomBuilder builder;
72  
73      @Inject
74      DefaultConsumerPomArtifactTransformer(ConsumerPomBuilder builder) {
75          this.builder = builder;
76      }
77  
78      @SuppressWarnings("deprecation")
79      public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException {
80          if (project.getFile() == null) {
81              // If there is no build POM there is no reason to inject artifacts for the consumer POM.
82              return;
83          }
84          if (Features.buildConsumer(session.getUserProperties())) {
85              Path buildDir =
86                      project.getBuild() != null ? Paths.get(project.getBuild().getDirectory()) : null;
87              if (buildDir != null) {
88                  Files.createDirectories(buildDir);
89              }
90              Path consumer = buildDir != null
91                      ? Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER + "-", ".pom")
92                      : Files.createTempFile(CONSUMER_POM_CLASSIFIER + "-", ".pom");
93              deferDeleteFile(consumer);
94  
95              project.addAttachedArtifact(createConsumerPomArtifact(project, consumer, session));
96          } else if (project.getModel().getDelegate().isRoot()) {
97              throw new IllegalStateException(
98                      "The use of the root attribute on the model requires the buildconsumer feature to be active");
99          }
100     }
101 
102     TransformedArtifact createConsumerPomArtifact(
103             MavenProject project, Path consumer, RepositorySystemSession session) {
104         return new TransformedArtifact(
105                 this,
106                 project,
107                 consumer,
108                 session,
109                 new ProjectArtifact(project),
110                 () -> project.getFile().toPath(),
111                 CONSUMER_POM_CLASSIFIER,
112                 "pom");
113     }
114 
115     void transform(MavenProject project, RepositorySystemSession session, Path src, Path tgt)
116             throws ModelBuildingException, ComponentLookupException, XMLStreamException, IOException {
117         Model model = builder.build(session, project, src);
118         write(model, tgt);
119     }
120 
121     private void deferDeleteFile(Path generatedFile) {
122         toDelete.add(generatedFile.toAbsolutePath());
123     }
124 
125     @PreDestroy
126     private void doDeleteFiles() {
127         for (Path file : toDelete) {
128             try {
129                 Files.delete(file);
130             } catch (IOException e) {
131                 // ignore, we did our best...
132             }
133         }
134     }
135 
136     public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) {
137         if (Features.buildConsumer(session.getUserProperties()) && consumerPomPresent(request.getArtifacts())) {
138             request.setArtifacts(replacePom(request.getArtifacts()));
139         }
140         return request;
141     }
142 
143     public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) {
144         if (Features.buildConsumer(session.getUserProperties()) && consumerPomPresent(request.getArtifacts())) {
145             request.setArtifacts(replacePom(request.getArtifacts()));
146         }
147         return request;
148     }
149 
150     private boolean consumerPomPresent(Collection<Artifact> artifacts) {
151         return artifacts.stream()
152                 .anyMatch(a -> "pom".equals(a.getExtension()) && CONSUMER_POM_CLASSIFIER.equals(a.getClassifier()));
153     }
154 
155     private Collection<Artifact> replacePom(Collection<Artifact> artifacts) {
156         List<Artifact> consumers = new ArrayList<>();
157         List<Artifact> mains = new ArrayList<>();
158         for (Artifact artifact : artifacts) {
159             if ("pom".equals(artifact.getExtension()) || artifact.getExtension().startsWith("pom.")) {
160                 if (CONSUMER_POM_CLASSIFIER.equals(artifact.getClassifier())) {
161                     consumers.add(artifact);
162                 } else if ("".equals(artifact.getClassifier())) {
163                     mains.add(artifact);
164                 }
165             }
166         }
167         if (!mains.isEmpty() && !consumers.isEmpty()) {
168             ArrayList<Artifact> result = new ArrayList<>(artifacts);
169             for (Artifact main : mains) {
170                 result.remove(main);
171                 result.add(new DefaultArtifact(
172                         main.getGroupId(),
173                         main.getArtifactId(),
174                         BUILD_POM_CLASSIFIER,
175                         main.getExtension(),
176                         main.getVersion(),
177                         main.getProperties(),
178                         main.getFile()));
179             }
180             for (Artifact consumer : consumers) {
181                 result.remove(consumer);
182                 result.add(new DefaultArtifact(
183                         consumer.getGroupId(),
184                         consumer.getArtifactId(),
185                         "",
186                         consumer.getExtension(),
187                         consumer.getVersion(),
188                         consumer.getProperties(),
189                         consumer.getFile()));
190             }
191             artifacts = result;
192         }
193         return artifacts;
194     }
195 
196     void write(Model model, Path dest) throws IOException, XMLStreamException {
197         String version = model.getModelVersion();
198         Files.createDirectories(dest.getParent());
199         try (Writer w = Files.newBufferedWriter(dest)) {
200             MavenStaxWriter writer = new MavenStaxWriter();
201             writer.setNamespace(String.format(NAMESPACE_FORMAT, version));
202             writer.setSchemaLocation(String.format(SCHEMA_LOCATION_FORMAT, version));
203             writer.setAddLocationInformation(false);
204             writer.write(w, model);
205         }
206     }
207 }