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 }