blob: e2cb735c4790afd53827f832b9a47798df6989c1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.plugins.war.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
/**
* Represents the structure of a web application composed of multiple overlays. Each overlay is registered within this
* structure with the set of files it holds.
*
* Note that this structure is persisted to disk at each invocation to store which owner holds which path (file).
*
* @author Stephane Nicoll
*/
public class WebappStructure {
private Map<String, PathSet> registeredFiles;
private List<DependencyInfo> dependenciesInfo;
private transient PathSet allFiles = new PathSet();
/**
* Creates a new empty instance.
*
* @param dependencies the dependencies of the project
*/
public WebappStructure(List<Dependency> dependencies) {
this.dependenciesInfo = createDependenciesInfoList(dependencies);
this.registeredFiles = new HashMap<>();
}
/**
* Returns the list of {@link DependencyInfo} for the project.
*
* @return the dependencies information of the project
*/
public List<DependencyInfo> getDependenciesInfo() {
return dependenciesInfo;
}
/**
* Returns the dependencies of the project.
*
* @return the dependencies of the project
*/
public List<Dependency> getDependencies() {
final List<Dependency> result = new ArrayList<>();
if (dependenciesInfo == null) {
return result;
}
for (DependencyInfo dependencyInfo : dependenciesInfo) {
result.add(dependencyInfo.getDependency());
}
return result;
}
/**
* Specify if the specified {@code path} is registered or not.
*
* @param path the relative path from the webapp root directory
* @return true if the path is registered, false otherwise
*/
public boolean isRegistered(String path) {
return getFullStructure().contains(path);
}
/**
* Registers the specified path for the specified owner. Returns {@code true} if the path is not already
* registered, {@code false} otherwise.
*
* @param id the owner of the path
* @param path the relative path from the webapp root directory
* @return true if the file was registered successfully
*/
public boolean registerFile(String id, String path) {
if (!isRegistered(path)) {
doRegister(id, path);
return true;
} else {
return false;
}
}
/**
* Forces the registration of the specified path for the specified owner. If the file is not registered yet, a
* simple registration is performed. If the file already exists, the owner changes to the specified one.
* <p>
* Beware that the semantic of the return boolean is different than the one from
* {@link #registerFile(String, String)}; returns {@code true} if an owner replacement was made and {@code false}
* if the file was simply registered for the first time.</p>
*
* @param id the owner of the path
* @param path the relative path from the webapp root directory
* @return false if the file did not exist, true if the owner was replaced
*/
public boolean registerFileForced(String id, String path) {
if (!isRegistered(path)) {
doRegister(id, path);
return false;
} else {
// Force the switch to the new owner
getStructure(getOwner(path)).remove(path);
getStructure(id).add(path);
return true;
}
}
/**
* Registers the specified path for the specified owner. Invokes the {@code callback} with the result of the
* registration.
*
* @param id the owner of the path
* @param path the relative path from the webapp root directory
* @param callback the callback to invoke with the result of the registration
* @throws IOException if the callback invocation throws an IOException
*/
public void registerFile(String id, String path, RegistrationCallback callback) throws IOException {
// If the file is already in the current structure, rejects it with the current owner
if (isRegistered(path)) {
callback.refused(id, path, getOwner(path));
} else {
doRegister(id, path);
// This is a new file
if (getOwner(path) == null) {
callback.registered(id, path);
} // The file already belonged to this owner
else if (getOwner(path).equals(id)) {
callback.alreadyRegistered(id, path);
} // The file belongs to another owner and it's known currently
else if (getOwners().contains(getOwner(path))) {
callback.superseded(id, path, getOwner(path));
} // The file belongs to another owner and it's unknown
else {
callback.supersededUnknownOwner(id, path, getOwner(path));
}
}
}
/**
* Returns the owner of the specified {@code path}. If the file is not registered, returns {@code null}
*
* @param path the relative path from the webapp root directory
* @return the owner or {@code null}.
*/
public String getOwner(String path) {
if (!isRegistered(path)) {
return null;
} else {
for (final String owner : registeredFiles.keySet()) {
final PathSet structure = getStructure(owner);
if (structure.contains(path)) {
return owner;
}
}
throw new IllegalStateException(
"Should not happen, path [" + path + "] is flagged as being registered but was not found.");
}
}
/**
* Returns the owners.
*
* @return the list of owners
*/
public Set<String> getOwners() {
return registeredFiles.keySet();
}
/**
* Returns all paths that have been registered so far.
*
* @return all registered path
*/
public PathSet getFullStructure() {
return allFiles;
}
/**
* Returns the list of registered files for the specified owner.
*
* @param id the owner
* @return the list of files registered for that owner
*/
public PathSet getStructure(String id) {
PathSet pathSet = registeredFiles.get(id);
if (pathSet == null) {
pathSet = new PathSet();
registeredFiles.put(id, pathSet);
}
return pathSet;
}
/**
* Registers the target file name for the specified artifact.
*
* @param artifact the artifact
* @param targetFileName the target file name
*/
public void registerTargetFileName(Artifact artifact, String targetFileName) {
if (dependenciesInfo != null) {
for (DependencyInfo dependencyInfo : dependenciesInfo) {
if (WarUtils.isRelated(artifact, dependencyInfo.getDependency())) {
dependencyInfo.setTargetFileName(targetFileName);
}
}
}
}
// Private helpers
private void doRegister(String id, String path) {
getFullStructure().add(path);
getStructure(id).add(path);
}
private List<DependencyInfo> createDependenciesInfoList(List<Dependency> dependencies) {
if (dependencies == null) {
return Collections.emptyList();
}
final List<DependencyInfo> result = new ArrayList<>();
for (Dependency dependency : dependencies) {
result.add(new DependencyInfo(dependency));
}
return result;
}
private Object readResolve() {
// the full structure should be resolved so let's rebuild it
this.allFiles = new PathSet();
for (PathSet pathSet : registeredFiles.values()) {
this.allFiles.addAll(pathSet);
}
return this;
}
/**
* Callback interface to handle events related to filepath registration in the webapp.
*/
public interface RegistrationCallback {
/**
* Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully.
*
* This means that the {@code targetFilename} was unknown and has been registered successfully.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @throws IOException if an error occurred while handling this event
*/
void registered(String ownerId, String targetFilename) throws IOException;
/**
* Called if the {@code targetFilename} for the specified {@code ownerId} has already been registered.
*
* This means that the {@code targetFilename} was known and belongs to the specified owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @throws IOException if an error occurred while handling this event
*/
void alreadyRegistered(String ownerId, String targetFilename) throws IOException;
/**
* <p>
* Called if the registration of the {@code targetFilename} for the specified {@code ownerId} has been refused
* since the path already belongs to the {@code actualOwnerId}.
* </p>
* This means that the {@code targetFilename} was known and does not belong to the specified owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param actualOwnerId the actual owner
* @throws IOException if an error occurred while handling this event
*/
void refused(String ownerId, String targetFilename, String actualOwnerId) throws IOException;
/**
* Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully by
* superseding a {@code deprecatedOwnerId}, that is the previous owner of the file.
*
* This means that the {@code targetFilename} was known but for another owner. This usually happens after a
* project's configuration change. As a result, the file has been registered successfully to the new owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param deprecatedOwnerId the previous owner that does not exist anymore
* @throws IOException if an error occurred while handling this event
*/
void superseded(String ownerId, String targetFilename, String deprecatedOwnerId) throws IOException;
/**
* Called if the {@code targetFilename} for the specified {@code ownerId} has been registered successfully by
* superseding a {@code unknownOwnerId}, that is an owner that does not exist anymore in the current project.
*
* This means that the {@code targetFilename} was known but for an owner that does not exist anymore. Hence the
* file has been registered successfully to the new owner.
*
* @param ownerId the ownerId
* @param targetFilename the relative path according to the root of the webapp
* @param unknownOwnerId the previous owner that does not exist anymore
* @throws IOException if an error occurred while handling this event
*/
void supersededUnknownOwner(String ownerId, String targetFilename, String unknownOwnerId) throws IOException;
}
}