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.plugins.war.overlay;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  import java.util.ListIterator;
25  import java.util.Objects;
26  import java.util.Set;
27  
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
30  import org.apache.maven.plugins.war.Overlay;
31  import org.apache.maven.project.MavenProject;
32  
33  /**
34   * Manages the overlays.
35   *
36   * @author Stephane Nicoll
37   */
38  public class OverlayManager {
39      private final List<Overlay> overlays;
40  
41      private final MavenProject project;
42  
43      private final List<Artifact> artifactsOverlays;
44  
45      /**
46       * Creates a manager with the specified overlays.
47       *
48       * Note that the list is potentially updated by the manager so a new list is created based on the overlays.
49       *
50       * @param overlays the overlays
51       * @param project the maven project
52       * @param defaultIncludes the default includes to use
53       * @param defaultExcludes the default excludes to use
54       * @param currentProjectOverlay the overlay for the current project
55       * @throws InvalidOverlayConfigurationException if the config is invalid
56       */
57      public OverlayManager(
58              List<Overlay> overlays,
59              MavenProject project,
60              String[] defaultIncludes,
61              String[] defaultExcludes,
62              Overlay currentProjectOverlay)
63              throws InvalidOverlayConfigurationException {
64          this.overlays = new ArrayList<>();
65          if (overlays != null) {
66              this.overlays.addAll(overlays);
67          }
68          this.project = project;
69  
70          this.artifactsOverlays = getOverlaysAsArtifacts();
71  
72          // Initialize
73          initialize(defaultIncludes, defaultExcludes, currentProjectOverlay);
74      }
75  
76      /**
77       * Returns the resolved overlays.
78       *
79       * @return the overlays
80       */
81      public List<Overlay> getOverlays() {
82          return overlays;
83      }
84  
85      /**
86       * Returns the id of the resolved overlays.
87       *
88       * @return the overlay ids
89       */
90      public List<String> getOverlayIds() {
91          final List<String> result = new ArrayList<>();
92          for (Overlay overlay : overlays) {
93              result.add(overlay.getId());
94          }
95          return result;
96      }
97  
98      /**
99       * Initializes the manager and validates the overlays configuration.
100      *
101      * @param defaultIncludes the default includes to use
102      * @param defaultExcludes the default excludes to use
103      * @param currentProjectOverlay the overlay for the current project
104      * @throws InvalidOverlayConfigurationException if the configuration is invalid
105      */
106     void initialize(String[] defaultIncludes, String[] defaultExcludes, Overlay currentProjectOverlay)
107             throws InvalidOverlayConfigurationException {
108 
109         // Build the list of configured artifacts and makes sure that each overlay
110         // refer to a valid artifact
111         final List<Artifact> configuredWarArtifacts = new ArrayList<>();
112         final ListIterator<Overlay> it = overlays.listIterator();
113         while (it.hasNext()) {
114             Overlay overlay = it.next();
115             if (overlay == null) {
116                 throw new InvalidOverlayConfigurationException("overlay could not be null.");
117             }
118             // If it's the current project, return the project instance
119             if (overlay.isCurrentProject()) {
120                 overlay = currentProjectOverlay;
121                 it.set(overlay);
122             }
123             // default includes/excludes - only if the overlay uses the default settings
124             if (Arrays.equals(Overlay.DEFAULT_INCLUDES, overlay.getIncludes())
125                     && Arrays.equals(Overlay.DEFAULT_EXCLUDES, overlay.getExcludes())) {
126                 overlay.setIncludes(defaultIncludes);
127                 overlay.setExcludes(defaultExcludes);
128             }
129 
130             final Artifact artifact = getAssociatedArtifact(overlay);
131             if (artifact != null) {
132                 configuredWarArtifacts.add(artifact);
133                 overlay.setArtifact(artifact);
134             }
135         }
136 
137         // Build the list of missing overlays
138         for (Artifact artifact : artifactsOverlays) {
139             if (!configuredWarArtifacts.contains(artifact)) {
140                 // Add a default overlay for the given artifact which will be applied after
141                 // the ones that have been configured
142                 overlays.add(new DefaultOverlay(artifact, defaultIncludes, defaultExcludes));
143             }
144         }
145 
146         // Final validation, make sure that the current project is in there. Otherwise add it first
147         for (Overlay overlay : overlays) {
148             if (overlay.equals(currentProjectOverlay)) {
149                 return;
150             }
151         }
152         overlays.add(0, currentProjectOverlay);
153     }
154 
155     /**
156      * Returns the Artifact associated to the specified overlay.
157      *
158      * If the overlay defines the current project, {@code null} is returned. If no artifact could not be found for the
159      * overlay a InvalidOverlayConfigurationException is thrown.
160      *
161      * @param overlay an overlay
162      * @return the artifact associated to the overlay
163      * @throws org.apache.maven.plugins.war.overlay.InvalidOverlayConfigurationException if the overlay does not have an
164      *             associated artifact
165      */
166     Artifact getAssociatedArtifact(final Overlay overlay) throws InvalidOverlayConfigurationException {
167         if (overlay.isCurrentProject()) {
168             return null;
169         }
170 
171         for (Artifact artifact : artifactsOverlays) {
172             // Handle classifier dependencies properly (clash management)
173             if (compareOverlayWithArtifact(overlay, artifact)) {
174                 return artifact;
175             }
176         }
177 
178         // maybe its a project dependencies zip or an other type
179         Set<Artifact> projectArtifacts = this.project.getDependencyArtifacts();
180         if (projectArtifacts != null) {
181             for (Artifact artifact : projectArtifacts) {
182                 if (compareOverlayWithArtifact(overlay, artifact)) {
183                     return artifact;
184                 }
185             }
186         }
187         // CHECKSTYLE_OFF: LineLength
188         throw new InvalidOverlayConfigurationException("overlay [" + overlay + "] is not a dependency of the project.");
189         // CHECKSTYLE_ON: LineLength
190 
191     }
192 
193     /**
194      * Compare groupId && artifactId && type && classifier.
195      *
196      * @param overlay the overlay
197      * @param artifact the artifact
198      * @return boolean true if equals
199      */
200     private boolean compareOverlayWithArtifact(Overlay overlay, Artifact artifact) {
201         return (Objects.equals(overlay.getGroupId(), artifact.getGroupId())
202                 && Objects.equals(overlay.getArtifactId(), artifact.getArtifactId())
203                 && Objects.equals(overlay.getType(), artifact.getType())
204                 // MWAR-241 Make sure to treat null and "" as equal when comparing the classifier
205                 && Objects.equals(
206                         Objects.toString(overlay.getClassifier()), Objects.toString(artifact.getClassifier())));
207     }
208 
209     /**
210      * Returns a list of WAR {@link org.apache.maven.artifact.Artifact} describing the overlays of the current project.
211      *
212      * @return the overlays as artifacts objects
213      */
214     private List<Artifact> getOverlaysAsArtifacts() {
215         ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
216         final Set<Artifact> artifacts = project.getArtifacts();
217 
218         final List<Artifact> result = new ArrayList<>();
219         for (Artifact artifact : artifacts) {
220             if (!artifact.isOptional() && filter.include(artifact) && ("war".equals(artifact.getType()))) {
221                 result.add(artifact);
222             }
223         }
224         return result;
225     }
226 }