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.util;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import org.apache.maven.artifact.Artifact;
30 import org.apache.maven.model.Dependency;
31
32 /**
33 * Represents the structure of a web application composed of multiple overlays. Each overlay is registered within this
34 * structure with the set of files it holds.
35 *
36 * Note that this structure is persisted to disk at each invocation to store which owner holds which path (file).
37 *
38 * @author Stephane Nicoll
39 */
40 public class WebappStructure {
41
42 private Map<String, PathSet> registeredFiles;
43
44 private List<DependencyInfo> dependenciesInfo;
45
46 private transient PathSet allFiles = new PathSet();
47
48 /**
49 * Creates a new empty instance.
50 *
51 * @param dependencies the dependencies of the project
52 */
53 public WebappStructure(List<Dependency> dependencies) {
54 this.dependenciesInfo = createDependenciesInfoList(dependencies);
55 this.registeredFiles = new HashMap<>();
56 }
57
58 /**
59 * Returns the list of {@link DependencyInfo} for the project.
60 *
61 * @return the dependencies information of the project
62 */
63 public List<DependencyInfo> getDependenciesInfo() {
64 return dependenciesInfo;
65 }
66
67 /**
68 * Returns the dependencies of the project.
69 *
70 * @return the dependencies of the project
71 */
72 public List<Dependency> getDependencies() {
73 final List<Dependency> result = new ArrayList<>();
74 if (dependenciesInfo == null) {
75 return result;
76 }
77 for (DependencyInfo dependencyInfo : dependenciesInfo) {
78 result.add(dependencyInfo.getDependency());
79 }
80 return result;
81 }
82
83 /**
84 * Specify if the specified {@code path} is registered or not.
85 *
86 * @param path the relative path from the webapp root directory
87 * @return true if the path is registered, false otherwise
88 */
89 public boolean isRegistered(String path) {
90 return getFullStructure().contains(path);
91 }
92
93 /**
94 * Registers the specified path for the specified owner. Returns {@code true} if the path is not already
95 * registered, {@code false} otherwise.
96 *
97 * @param id the owner of the path
98 * @param path the relative path from the webapp root directory
99 * @return true if the file was registered successfully
100 */
101 public boolean registerFile(String id, String path) {
102 if (!isRegistered(path)) {
103 doRegister(id, path);
104 return true;
105 } else {
106 return false;
107 }
108 }
109
110 /**
111 * Forces the registration of the specified path for the specified owner. If the file is not registered yet, a
112 * simple registration is performed. If the file already exists, the owner changes to the specified one.
113 * <p>
114 * Beware that the semantic of the return boolean is different than the one from
115 * {@link #registerFile(String, String)}; returns {@code true} if an owner replacement was made and {@code false}
116 * if the file was simply registered for the first time.</p>
117 *
118 * @param id the owner of the path
119 * @param path the relative path from the webapp root directory
120 * @return false if the file did not exist, true if the owner was replaced
121 */
122 public boolean registerFileForced(String id, String path) {
123 if (!isRegistered(path)) {
124 doRegister(id, path);
125 return false;
126 } else {
127 // Force the switch to the new owner
128 getStructure(getOwner(path)).remove(path);
129 getStructure(id).add(path);
130 return true;
131 }
132 }
133
134 /**
135 * Registers the specified path for the specified owner. Invokes the {@code callback} with the result of the
136 * registration.
137 *
138 * @param id the owner of the path
139 * @param path the relative path from the webapp root directory
140 * @param callback the callback to invoke with the result of the registration
141 * @throws IOException if the callback invocation throws an IOException
142 */
143 public void registerFile(String id, String path, RegistrationCallback callback) throws IOException {
144
145 // If the file is already in the current structure, rejects it with the current owner
146 if (isRegistered(path)) {
147 callback.refused(id, path, getOwner(path));
148 } else {
149 doRegister(id, path);
150 // This is a new file
151 if (getOwner(path) == null) {
152 callback.registered(id, path);
153
154 } // The file already belonged to this owner
155 else if (getOwner(path).equals(id)) {
156 callback.alreadyRegistered(id, path);
157 } // The file belongs to another owner and it's known currently
158 else if (getOwners().contains(getOwner(path))) {
159 callback.superseded(id, path, getOwner(path));
160 } // The file belongs to another owner and it's unknown
161 else {
162 callback.supersededUnknownOwner(id, path, getOwner(path));
163 }
164 }
165 }
166
167 /**
168 * Returns the owner of the specified {@code path}. If the file is not registered, returns {@code null}
169 *
170 * @param path the relative path from the webapp root directory
171 * @return the owner or {@code null}.
172 */
173 public String getOwner(String path) {
174 if (!isRegistered(path)) {
175 return null;
176 } else {
177 for (final String owner : registeredFiles.keySet()) {
178 final PathSet structure = getStructure(owner);
179 if (structure.contains(path)) {
180 return owner;
181 }
182 }
183 throw new IllegalStateException(
184 "Should not happen, path [" + path + "] is flagged as being registered but was not found.");
185 }
186 }
187
188 /**
189 * Returns the owners.
190 *
191 * @return the list of owners
192 */
193 public Set<String> getOwners() {
194 return registeredFiles.keySet();
195 }
196
197 /**
198 * Returns all paths that have been registered so far.
199 *
200 * @return all registered path
201 */
202 public PathSet getFullStructure() {
203 return allFiles;
204 }
205
206 /**
207 * Returns the list of registered files for the specified owner.
208 *
209 * @param id the owner
210 * @return the list of files registered for that owner
211 */
212 public PathSet getStructure(String id) {
213 PathSet pathSet = registeredFiles.get(id);
214 if (pathSet == null) {
215 pathSet = new PathSet();
216 registeredFiles.put(id, pathSet);
217 }
218 return pathSet;
219 }
220
221 /**
222 * Registers the target file name for the specified artifact.
223 *
224 * @param artifact the artifact
225 * @param targetFileName the target file name
226 */
227 public void registerTargetFileName(Artifact artifact, String targetFileName) {
228 if (dependenciesInfo != null) {
229 for (DependencyInfo dependencyInfo : dependenciesInfo) {
230 if (WarUtils.isRelated(artifact, dependencyInfo.getDependency())) {
231 dependencyInfo.setTargetFileName(targetFileName);
232 }
233 }
234 }
235 }
236
237 // Private helpers
238
239 private void doRegister(String id, String path) {
240 getFullStructure().add(path);
241 getStructure(id).add(path);
242 }
243
244 private List<DependencyInfo> createDependenciesInfoList(List<Dependency> dependencies) {
245 if (dependencies == null) {
246 return Collections.emptyList();
247 }
248 final List<DependencyInfo> result = new ArrayList<>();
249 for (Dependency dependency : dependencies) {
250 result.add(new DependencyInfo(dependency));
251 }
252 return result;
253 }
254
255 private Object readResolve() {
256 // the full structure should be resolved so let's rebuild it
257 this.allFiles = new PathSet();
258 for (PathSet pathSet : registeredFiles.values()) {
259 this.allFiles.addAll(pathSet);
260 }
261 return this;
262 }
263
264 /**
265 * Callback interface to handle events related to filepath registration in the webapp.
266 */
267 public interface RegistrationCallback {
268
269 /**
270 * Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully.
271 *
272 * This means that the {@code targetFilename} was unknown and has been registered successfully.
273 *
274 * @param ownerId the ownerId
275 * @param targetFilename the relative path according to the root of the webapp
276 * @throws IOException if an error occurred while handling this event
277 */
278 void registered(String ownerId, String targetFilename) throws IOException;
279
280 /**
281 * Called if the {@code targetFilename} for the specified {@code ownerId} has already been registered.
282 *
283 * This means that the {@code targetFilename} was known and belongs to the specified owner.
284 *
285 * @param ownerId the ownerId
286 * @param targetFilename the relative path according to the root of the webapp
287 * @throws IOException if an error occurred while handling this event
288 */
289 void alreadyRegistered(String ownerId, String targetFilename) throws IOException;
290
291 /**
292 * <p>
293 * Called if the registration of the {@code targetFilename} for the specified {@code ownerId} has been refused
294 * since the path already belongs to the {@code actualOwnerId}.
295 * </p>
296 * This means that the {@code targetFilename} was known and does not belong to the specified owner.
297 *
298 * @param ownerId the ownerId
299 * @param targetFilename the relative path according to the root of the webapp
300 * @param actualOwnerId the actual owner
301 * @throws IOException if an error occurred while handling this event
302 */
303 void refused(String ownerId, String targetFilename, String actualOwnerId) throws IOException;
304
305 /**
306 * Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully by
307 * superseding a {@code deprecatedOwnerId}, that is the previous owner of the file.
308 *
309 * This means that the {@code targetFilename} was known but for another owner. This usually happens after a
310 * project's configuration change. As a result, the file has been registered successfully to the new owner.
311 *
312 * @param ownerId the ownerId
313 * @param targetFilename the relative path according to the root of the webapp
314 * @param deprecatedOwnerId the previous owner that does not exist anymore
315 * @throws IOException if an error occurred while handling this event
316 */
317 void superseded(String ownerId, String targetFilename, String deprecatedOwnerId) throws IOException;
318
319 /**
320 * Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully by
321 * superseding a {@code unknownOwnerId}, that is an owner that does not exist anymore in the current project.
322 *
323 * This means that the {@code targetFilename} was known but for an owner that does not exist anymore. Hence the
324 * file has been registered successfully to the new owner.
325 *
326 * @param ownerId the ownerId
327 * @param targetFilename the relative path according to the root of the webapp
328 * @param unknownOwnerId the previous owner that does not exist anymore
329 * @throws IOException if an error occurred while handling this event
330 */
331 void supersededUnknownOwner(String ownerId, String targetFilename, String unknownOwnerId) throws IOException;
332 }
333 }