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 }