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.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 }