ResourceHandlingContext.java
/*
* 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.project;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import org.apache.maven.api.Language;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.model.Resource;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.ModelBuilderResult;
import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.impl.DefaultSourceRoot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles resource configuration for Maven projects.
* Groups parameters shared between main and test resource handling.
*/
class ResourceHandlingContext {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceHandlingContext.class);
private final MavenProject project;
private final Path baseDir;
private final Set<String> modules;
private final boolean modularProject;
private final ModelBuilderResult result;
ResourceHandlingContext(
MavenProject project,
Path baseDir,
Set<String> modules,
boolean modularProject,
ModelBuilderResult result) {
this.project = project;
this.baseDir = baseDir;
this.modules = modules;
this.modularProject = modularProject;
this.result = result;
}
/**
* Handles resource configuration for a given scope (main or test).
* This method applies the resource priority rules:
* <ol>
* <li>Modular project: use resources from {@code <sources>} if present, otherwise inject defaults</li>
* <li>Classic project: use resources from {@code <sources>} if present, otherwise use legacy resources</li>
* </ol>
*
* @param scope the project scope (MAIN or TEST)
* @param hasResourcesInSources whether resources are configured via {@code <sources>}
*/
void handleResourceConfiguration(ProjectScope scope, boolean hasResourcesInSources) {
List<Resource> resources = scope == ProjectScope.MAIN
? project.getBuild().getDelegate().getResources()
: project.getBuild().getDelegate().getTestResources();
String scopeId = scope.id();
String scopeName = scope == ProjectScope.MAIN ? "Main" : "Test";
String legacyElement = scope == ProjectScope.MAIN ? "<resources>" : "<testResources>";
String sourcesConfig = scope == ProjectScope.MAIN
? "<source><lang>resources</lang></source>"
: "<source><lang>resources</lang><scope>test</scope></source>";
if (modularProject) {
if (hasResourcesInSources) {
// Modular project with resources configured via <sources> - already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
LOGGER.warn(
"Legacy {} element is ignored because {} resources are configured via {} in <sources>.",
legacyElement,
scopeId,
sourcesConfig);
} else {
LOGGER.debug(
"{} resources configured via <sources> element, ignoring legacy {} element.",
scopeName,
legacyElement);
}
} else {
// Modular project without resources in <sources> - inject module-aware defaults
if (hasExplicitLegacyResources(resources, scopeId)) {
String message = "Legacy " + legacyElement
+ " element is ignored because modular sources are configured. "
+ "Use " + sourcesConfig + " in <sources> for custom resource paths.";
LOGGER.warn(message);
result.getProblemCollector()
.reportProblem(new org.apache.maven.impl.model.DefaultModelProblem(
message,
Severity.WARNING,
Version.V41,
project.getModel().getDelegate(),
-1,
-1,
null));
}
for (String module : modules) {
project.addSourceRoot(createModularResourceRoot(module, scope));
}
if (!modules.isEmpty()) {
LOGGER.debug(
"Injected {} module-aware {} resource root(s) for modules: {}.",
modules.size(),
scopeId,
modules);
}
}
} else {
// Classic (non-modular) project
if (hasResourcesInSources) {
// Resources configured via <sources> - already added above
if (hasExplicitLegacyResources(resources, scopeId)) {
LOGGER.warn(
"Legacy {} element is ignored because {} resources are configured via {} in <sources>.",
legacyElement,
scopeId,
sourcesConfig);
} else {
LOGGER.debug(
"{} resources configured via <sources> element, ignoring legacy {} element.",
scopeName,
legacyElement);
}
} else {
// Use legacy resources element
LOGGER.debug(
"Using explicit or default {} resources ({} resources configured).", scopeId, resources.size());
for (Resource resource : resources) {
project.addSourceRoot(new DefaultSourceRoot(baseDir, scope, resource));
}
}
}
}
/**
* Creates a DefaultSourceRoot for module-aware resource directories.
* Generates paths following the pattern: {@code src/<module>/<scope>/resources}
*
* @param module module name
* @param scope project scope (main or test)
* @return configured DefaultSourceRoot for the module's resources
*/
private DefaultSourceRoot createModularResourceRoot(String module, ProjectScope scope) {
Path resourceDir =
baseDir.resolve("src").resolve(module).resolve(scope.id()).resolve("resources");
return new DefaultSourceRoot(
scope,
Language.RESOURCES,
module,
null, // targetVersion
resourceDir,
null, // includes
null, // excludes
false, // stringFiltering
Path.of(module), // targetPath - resources go to target/classes/<module>
true // enabled
);
}
/**
* Checks if the given resource list contains explicit legacy resources that differ
* from Super POM defaults. Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered
*
* @param resources list of resources to check
* @param scope scope (main or test)
* @return true if explicit legacy resources are present that would be ignored
*/
private boolean hasExplicitLegacyResources(List<Resource> resources, String scope) {
if (resources.isEmpty()) {
return false; // No resources means no explicit legacy resources to warn about
}
// Super POM default paths
String defaultPath =
baseDir.resolve("src").resolve(scope).resolve("resources").toString();
String defaultFilteredPath = baseDir.resolve("src")
.resolve(scope)
.resolve("resources-filtered")
.toString();
// Check if any resource differs from Super POM defaults
for (Resource resource : resources) {
String resourceDir = resource.getDirectory();
if (resourceDir != null && !resourceDir.equals(defaultPath) && !resourceDir.equals(defaultFilteredPath)) {
// Found an explicit legacy resource
return true;
}
}
return false;
}
}