CacheConfigurationResolver.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.impl.cache;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.maven.api.Constants;
import org.apache.maven.api.Session;
import org.apache.maven.api.cache.CacheMetadata;
import org.apache.maven.api.cache.CacheRetention;
import org.apache.maven.api.services.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Resolves cache configuration for requests based on user-defined selectors.
*/
public class CacheConfigurationResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(CacheConfigurationResolver.class);
/**
* Cache for parsed selectors per session to avoid re-parsing.
*/
private static final ConcurrentMap<String, List<CacheSelector>> SELECTOR_CACHE = new ConcurrentHashMap<>();
/**
* Resolves cache configuration for the given request and session.
*
* @param req the request to resolve configuration for
* @param session the session containing user properties
* @return the resolved cache configuration
*/
public static CacheConfig resolveConfig(Request<?> req, Session session) {
// First check if request implements CacheMetadata for backward compatibility
CacheRetention legacyRetention = null;
if (req instanceof CacheMetadata metadata) {
legacyRetention = metadata.getCacheRetention();
}
// Check for key reference type configuration
Cache.ReferenceType keyRefType = null;
String keyRefsString = session.getUserProperties().get(Constants.MAVEN_CACHE_KEY_REFS);
if (keyRefsString != null && !keyRefsString.trim().isEmpty()) {
try {
keyRefType = Cache.ReferenceType.valueOf(keyRefsString.trim().toUpperCase());
} catch (IllegalArgumentException e) {
LOGGER.warn("Invalid key reference types '{}', using defaults", keyRefsString);
}
}
// Check for value reference type configuration
Cache.ReferenceType valueRefType = null;
String valueRefsString = session.getUserProperties().get(Constants.MAVEN_CACHE_VALUE_REFS);
if (valueRefsString != null && !valueRefsString.trim().isEmpty()) {
try {
valueRefType =
Cache.ReferenceType.valueOf(valueRefsString.trim().toUpperCase());
} catch (IllegalArgumentException e) {
LOGGER.warn("Invalid value reference types '{}', using defaults", valueRefsString);
}
}
// Get user-defined configuration
String configString = session.getUserProperties().get(Constants.MAVEN_CACHE_CONFIG_PROPERTY);
if (configString == null || configString.trim().isEmpty()) {
// No user configuration, use legacy behavior or defaults
if (legacyRetention != null) {
CacheConfig config = new CacheConfig(
legacyRetention, getDefaultReferenceType(legacyRetention), keyRefType, valueRefType);
return config;
}
if (keyRefType != null && valueRefType != null) {
return new CacheConfig(
CacheConfig.DEFAULT.scope(), CacheConfig.DEFAULT.referenceType(), keyRefType, valueRefType);
}
return CacheConfig.DEFAULT;
}
// Parse and cache selectors
List<CacheSelector> selectors = SELECTOR_CACHE.computeIfAbsent(configString, CacheSelectorParser::parse);
// Find all matching selectors and merge them (most specific first)
PartialCacheConfig mergedConfig = null;
for (CacheSelector selector : selectors) {
if (selector.matches(req)) {
if (mergedConfig == null) {
mergedConfig = selector.config();
LOGGER.debug(
"Cache config for {}: matched selector '{}' with config {}",
req.getClass().getSimpleName(),
selector,
selector.config());
} else {
PartialCacheConfig previousConfig = mergedConfig;
mergedConfig = mergedConfig.mergeWith(selector.config());
LOGGER.debug(
"Cache config for {}: merged selector '{}' with previous config {} -> {}",
req.getClass().getSimpleName(),
selector,
previousConfig,
mergedConfig);
}
// If we have a complete configuration, we can stop
if (mergedConfig.isComplete()) {
break;
}
}
}
// Convert merged partial config to complete config
if (mergedConfig != null && !mergedConfig.isEmpty()) {
CacheConfig finalConfig = mergedConfig.toComplete();
// Apply key/value reference types if specified
if (keyRefType != null && valueRefType != null) {
finalConfig =
new CacheConfig(finalConfig.scope(), finalConfig.referenceType(), keyRefType, valueRefType);
}
LOGGER.debug("Final cache config for {}: {}", req.getClass().getSimpleName(), finalConfig);
return finalConfig;
}
// No selector matched, use legacy behavior or defaults
if (legacyRetention != null) {
CacheConfig config = new CacheConfig(
legacyRetention, getDefaultReferenceType(legacyRetention), keyRefType, valueRefType);
LOGGER.debug(
"Cache config for {}: {} (legacy CacheMetadata)",
req.getClass().getSimpleName(),
config);
return config;
}
if (keyRefType != null && valueRefType != null) {
CacheConfig config = new CacheConfig(
CacheConfig.DEFAULT.scope(), CacheConfig.DEFAULT.referenceType(), keyRefType, valueRefType);
LOGGER.debug(
"Cache config for {}: {} (with key/value refs)",
req.getClass().getSimpleName(),
config);
return config;
}
LOGGER.debug("Cache config for {}: {} (default)", req.getClass().getSimpleName(), CacheConfig.DEFAULT);
return CacheConfig.DEFAULT;
}
/**
* Gets the default reference type for a given cache retention.
* This maintains backward compatibility with the original hardcoded behavior.
*/
private static Cache.ReferenceType getDefaultReferenceType(CacheRetention retention) {
return switch (retention) {
case SESSION_SCOPED -> Cache.ReferenceType.SOFT;
case REQUEST_SCOPED -> Cache.ReferenceType.SOFT; // Changed from HARD to SOFT for consistency
case PERSISTENT -> Cache.ReferenceType.HARD;
case DISABLED -> Cache.ReferenceType.NONE;
};
}
/**
* Clears the selector cache. Useful for testing.
*/
public static void clearCache() {
SELECTOR_CACHE.clear();
}
}