RequestTraceHelper.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;

import java.util.stream.Collectors;

import org.apache.maven.api.Session;
import org.apache.maven.api.services.Request;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectStepData;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.DependencyRequest;

/**
 * Helper class to manage request tracing for improved error logging in Maven's dependency resolution.
 * This class provides utilities to:
 * - Track request traces through Maven's dependency resolution process
 * - Convert between Maven and Resolver trace formats
 * - Generate human-readable interpretations of trace data
 */
public final class RequestTraceHelper {

    /**
     * Represents a resolver trace containing both Maven and Resolver-specific trace information
     * @param session The current Maven session
     * @param context The trace context
     * @param trace The Resolver-specific trace
     * @param mvnTrace The Maven-specific trace
     */
    public record ResolverTrace(
            Session session, String context, RequestTrace trace, org.apache.maven.api.services.RequestTrace mvnTrace) {}

    /**
     * Creates a new trace entry and updates the session's current trace
     * @param session The current Maven session
     * @param data The data object to associate with the trace
     * @return A new ResolverTrace containing both Maven and Resolver trace information
     */
    public static ResolverTrace enter(Session session, Object data) {
        InternalSession iSession = InternalSession.from(session);
        org.apache.maven.api.services.RequestTrace trace = data instanceof Request<?> req && req.getTrace() != null
                ? req.getTrace()
                : new org.apache.maven.api.services.RequestTrace(iSession.getCurrentTrace(), data);
        iSession.setCurrentTrace(trace);
        return new ResolverTrace(session, trace.context(), toResolver(trace), trace);
    }

    /**
     * Restores the parent trace as the current trace in the session
     * @param trace The current resolver trace to exit from
     */
    public static void exit(ResolverTrace trace) {
        InternalSession iSession = InternalSession.from(trace.session());
        iSession.setCurrentTrace(trace.mvnTrace().parent());
    }

    /**
     * Converts a Resolver trace to a Maven trace
     * @param context The context string for the new Maven trace
     * @param trace The Resolver trace to convert
     * @return A new Maven trace, or null if the input trace was null
     */
    public static org.apache.maven.api.services.RequestTrace toMaven(String context, RequestTrace trace) {
        if (trace != null) {
            return new org.apache.maven.api.services.RequestTrace(
                    context, toMaven(context, trace.getParent()), trace.getData());
        } else {
            return null;
        }
    }

    /**
     * Converts a Maven trace to a Resolver trace
     * @param trace The Maven trace to convert
     * @return A new Resolver trace, or null if the input trace was null
     */
    public static RequestTrace toResolver(org.apache.maven.api.services.RequestTrace trace) {
        if (trace != null) {
            return RequestTrace.newChild(toResolver(trace.parent()), trace.data());
        } else {
            return null;
        }
    }

    /**
     * Creates a human-readable interpretation of a request trace
     * @param detailed If true, includes additional details such as dependency paths
     * @param requestTrace The trace to interpret
     * @return A string describing the trace context and relevant details
     */
    public static String interpretTrace(boolean detailed, RequestTrace requestTrace) {
        while (requestTrace != null) {
            Object data = requestTrace.getData();
            if (data instanceof DependencyRequest request) {
                return "dependency resolution for " + request;
            } else if (data instanceof CollectRequest request) {
                return "dependency collection for " + request;
            } else if (data instanceof CollectStepData stepData) {
                String msg = "dependency collection step for " + stepData.getContext();
                if (detailed) {
                    msg += ". Path to offending node from root:\n";
                    msg += stepData.getPath().stream()
                            .map(n -> " -> " + n.toString())
                            .collect(Collectors.joining("\n"));
                    msg += "\n => " + stepData.getNode();
                }
                return msg;
            } else if (data instanceof ArtifactDescriptorRequest request) {
                return "artifact descriptor request for " + request.getArtifact();
            } else if (data instanceof ArtifactRequest request) {
                return "artifact request for " + request.getArtifact();
            } else if (data instanceof org.apache.maven.api.model.Plugin plugin) {
                return "plugin " + plugin.getId();
            }
            requestTrace = requestTrace.getParent();
        }

        return "n/a";
    }
}