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 }