View Javadoc
1   package org.apache.maven.plugins.war.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.model.Dependency;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  /**
34   * Represents the structure of a web application composed of multiple overlays. Each overlay is registered within this
35   * structure with the set of files it holds.
36   * 
37   * Note that this structure is persisted to disk at each invocation to store which owner holds which path (file).
38   *
39   * @author Stephane Nicoll
40   */
41  public class WebappStructure
42  {
43  
44      private Map<String, PathSet> registeredFiles;
45  
46      private List<DependencyInfo> dependenciesInfo;
47  
48      private transient PathSet allFiles = new PathSet();
49  
50      /**
51       * Creates a new empty instance.
52       *
53       * @param dependencies the dependencies of the project
54       */
55      public WebappStructure( List<Dependency> dependencies )
56      {
57          this.dependenciesInfo = createDependenciesInfoList( dependencies );
58          this.registeredFiles = new HashMap<>();
59      }
60  
61      /**
62       * Returns the list of {@link DependencyInfo} for the project.
63       *
64       * @return the dependencies information of the project
65       */
66      public List<DependencyInfo> getDependenciesInfo()
67      {
68          return dependenciesInfo;
69      }
70  
71      /**
72       * Returns the dependencies of the project.
73       *
74       * @return the dependencies of the project
75       */
76      public List<Dependency> getDependencies()
77      {
78          final List<Dependency> result = new ArrayList<>();
79          if ( dependenciesInfo == null )
80          {
81              return result;
82          }
83          for ( DependencyInfo dependencyInfo : dependenciesInfo )
84          {
85              result.add( dependencyInfo.getDependency() );
86          }
87          return result;
88      }
89  
90      /**
91       * Specify if the specified <tt>path</tt> is registered or not.
92       *
93       * @param path the relative path from the webapp root directory
94       * @return true if the path is registered, false otherwise
95       */
96      public boolean isRegistered( String path )
97      {
98          return getFullStructure().contains( path );
99  
100     }
101 
102     /**
103      * Registers the specified path for the specified owner. Returns <tt>true</tt> if the path is not already
104      * registered, <tt>false</tt> otherwise.
105      *
106      * @param id the owner of the path
107      * @param path the relative path from the webapp root directory
108      * @return true if the file was registered successfully
109      */
110     public boolean registerFile( String id, String path )
111     {
112         if ( !isRegistered( path ) )
113         {
114             doRegister( id, path );
115             return true;
116         }
117         else
118         {
119             return false;
120         }
121     }
122 
123     /**
124      * Forces the registration of the specified path for the specified owner. If the file is not registered yet, a
125      * simple registration is performed. If the file already exists, the owner changes to the specified one.
126      * <p>
127      * Beware that the semantic of the return boolean is different than the one from
128      * {@link #registerFile(String, String)}; returns <tt>true</tt> if an owner replacement was made and <tt>false</tt>
129      * if the file was simply registered for the first time.</p>
130      *
131      * @param id the owner of the path
132      * @param path the relative path from the webapp root directory
133      * @return false if the file did not exist, true if the owner was replaced
134      */
135     public boolean registerFileForced( String id, String path )
136     {
137         if ( !isRegistered( path ) )
138         {
139             doRegister( id, path );
140             return false;
141         }
142         else
143         {
144             // Force the switch to the new owner
145             getStructure( getOwner( path ) ).remove( path );
146             getStructure( id ).add( path );
147             return true;
148         }
149 
150     }
151 
152     /**
153      * Registers the specified path for the specified owner. Invokes the <tt>callback</tt> with the result of the
154      * registration.
155      *
156      * @param id the owner of the path
157      * @param path the relative path from the webapp root directory
158      * @param callback the callback to invoke with the result of the registration
159      * @throws IOException if the callback invocation throws an IOException
160      */
161     public void registerFile( String id, String path, RegistrationCallback callback )
162         throws IOException
163     {
164 
165         // If the file is already in the current structure, rejects it with the current owner
166         if ( isRegistered( path ) )
167         {
168             callback.refused( id, path, getOwner( path ) );
169         }
170         else
171         {
172             doRegister( id, path );
173             // This is a new file
174             if ( getOwner( path ) == null )
175             {
176                 callback.registered( id, path );
177 
178             } // The file already belonged to this owner
179             else if ( getOwner( path ).equals( id ) )
180             {
181                 callback.alreadyRegistered( id, path );
182             } // The file belongs to another owner and it's known currently
183             else if ( getOwners().contains( getOwner( path ) ) )
184             {
185                 callback.superseded( id, path, getOwner( path ) );
186             } // The file belongs to another owner and it's unknown
187             else
188             {
189                 callback.supersededUnknownOwner( id, path, getOwner( path ) );
190             }
191         }
192     }
193 
194     /**
195      * Returns the owner of the specified <tt>path</tt>. If the file is not registered, returns <tt>null</tt>
196      *
197      * @param path the relative path from the webapp root directory
198      * @return the owner or <tt>null</tt>.
199      */
200     public String getOwner( String path )
201     {
202         if ( !isRegistered( path ) )
203         {
204             return null;
205         }
206         else
207         {
208             for ( final String owner : registeredFiles.keySet() )
209             {
210                 final PathSet structure = getStructure( owner );
211                 if ( structure.contains( path ) )
212                 {
213                     return owner;
214                 }
215 
216             }
217             throw new IllegalStateException( "Should not happen, path [" + path
218                 + "] is flagged as being registered but was not found." );
219         }
220 
221     }
222 
223     /**
224      * Returns the owners.
225      *
226      * @return the list of owners
227      */
228     public Set<String> getOwners()
229     {
230         return registeredFiles.keySet();
231     }
232 
233     /**
234      * Returns all paths that have been registered so far.
235      *
236      * @return all registered path
237      */
238     public PathSet getFullStructure()
239     {
240         return allFiles;
241     }
242 
243     /**
244      * Returns the list of registered files for the specified owner.
245      *
246      * @param id the owner
247      * @return the list of files registered for that owner
248      */
249     public PathSet getStructure( String id )
250     {
251         PathSet pathSet = registeredFiles.get( id );
252         if ( pathSet == null )
253         {
254             pathSet = new PathSet();
255             registeredFiles.put( id, pathSet );
256         }
257         return pathSet;
258     }
259 
260     
261 
262     /**
263      * Registers the target file name for the specified artifact.
264      *
265      * @param artifact the artifact
266      * @param targetFileName the target file name
267      */
268     public void registerTargetFileName( Artifact artifact, String targetFileName )
269     {
270         if ( dependenciesInfo != null )
271         {
272             for ( DependencyInfo dependencyInfo : dependenciesInfo )
273             {
274                 if ( WarUtils.isRelated( artifact, dependencyInfo.getDependency() ) )
275                 {
276                     dependencyInfo.setTargetFileName( targetFileName );
277                 }
278             }
279         }
280     }
281 
282     // Private helpers
283 
284     private void doRegister( String id, String path )
285     {
286         getFullStructure().add( path );
287         getStructure( id ).add( path );
288     }
289 
290     private List<DependencyInfo> createDependenciesInfoList( List<Dependency> dependencies )
291     {
292         if ( dependencies == null )
293         {
294             return Collections.emptyList();
295         }
296         final List<DependencyInfo> result = new ArrayList<>();
297         for ( Dependency dependency : dependencies )
298         {
299             result.add( new DependencyInfo( dependency ) );
300         }
301         return result;
302     }
303 
304     private Object readResolve()
305     {
306         // the full structure should be resolved so let's rebuild it
307         this.allFiles = new PathSet();
308         for ( PathSet pathSet : registeredFiles.values() )
309         {
310             this.allFiles.addAll( pathSet );
311         }
312         return this;
313     }
314 
315     /**
316      * Callback interface to handle events related to filepath registration in the webapp.
317      */
318     public interface RegistrationCallback
319     {
320 
321         /**
322          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt> has been registered successfully.
323          * 
324          * This means that the <tt>targetFilename</tt> was unknown and has been registered successfully.
325          *
326          * @param ownerId the ownerId
327          * @param targetFilename the relative path according to the root of the webapp
328          * @throws IOException if an error occurred while handling this event
329          */
330         void registered( String ownerId, String targetFilename )
331             throws IOException;
332 
333         /**
334          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt> has already been registered.
335          * 
336          * This means that the <tt>targetFilename</tt> was known and belongs to the specified owner.
337          *
338          * @param ownerId the ownerId
339          * @param targetFilename the relative path according to the root of the webapp
340          * @throws IOException if an error occurred while handling this event
341          */
342         void alreadyRegistered( String ownerId, String targetFilename )
343             throws IOException;
344 
345         /**
346          * <p>
347          * Called if the registration of the <tt>targetFilename</tt> for the specified <tt>ownerId</tt> has been refused
348          * since the path already belongs to the <tt>actualOwnerId</tt>.
349          * </p> 
350          * This means that the <tt>targetFilename</tt> was known and does not belong to the specified owner.
351          *
352          * @param ownerId the ownerId
353          * @param targetFilename the relative path according to the root of the webapp
354          * @param actualOwnerId the actual owner
355          * @throws IOException if an error occurred while handling this event
356          */
357         void refused( String ownerId, String targetFilename, String actualOwnerId )
358             throws IOException;
359 
360         /**
361          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt> has been registered successfully by
362          * superseding a <tt>deprecatedOwnerId</tt>, that is the previous owner of the file.
363          * 
364          * This means that the <tt>targetFilename</tt> was known but for another owner. This usually happens after a
365          * project's configuration change. As a result, the file has been registered successfully to the new owner.
366          *
367          * @param ownerId the ownerId
368          * @param targetFilename the relative path according to the root of the webapp
369          * @param deprecatedOwnerId the previous owner that does not exist anymore
370          * @throws IOException if an error occurred while handling this event
371          */
372         void superseded( String ownerId, String targetFilename, String deprecatedOwnerId )
373             throws IOException;
374 
375         /**
376          * Called if the <tt>targetFilename</tt> for the specified <tt>ownerId</tt> has been registered successfully by
377          * superseding a <tt>unknownOwnerId</tt>, that is an owner that does not exist anymore in the current project.
378          * 
379          * This means that the <tt>targetFilename</tt> was known but for an owner that does not exist anymore. Hence the
380          * file has been registered successfully to the new owner.
381          *
382          * @param ownerId the ownerId
383          * @param targetFilename the relative path according to the root of the webapp
384          * @param unknownOwnerId the previous owner that does not exist anymore
385          * @throws IOException if an error occurred while handling this event
386          */
387         void supersededUnknownOwner( String ownerId, String targetFilename, String unknownOwnerId )
388             throws IOException;
389     }
390 
391 }