001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.resolution;
020
021import java.util.Collections;
022import java.util.List;
023
024import org.eclipse.aether.RepositoryException;
025import org.eclipse.aether.repository.LocalArtifactResult;
026import org.eclipse.aether.transfer.ArtifactFilteredOutException;
027import org.eclipse.aether.transfer.ArtifactNotFoundException;
028import org.eclipse.aether.transfer.RepositoryOfflineException;
029
030/**
031 * Thrown in case of a unresolvable artifacts.
032 */
033public class ArtifactResolutionException extends RepositoryException {
034    private final transient List<ArtifactResult> results;
035
036    /**
037     * Creates a new exception with the specified results.
038     *
039     * @param results The resolution results at the point the exception occurred, may be {@code null}.
040     */
041    public ArtifactResolutionException(List<ArtifactResult> results) {
042        super(getMessage(results), getCause(results));
043        this.results = results != null ? results : Collections.emptyList();
044    }
045
046    /**
047     * Creates a new exception with the specified results and detail message.
048     *
049     * @param results The resolution results at the point the exception occurred, may be {@code null}.
050     * @param message The detail message, may be {@code null}.
051     */
052    public ArtifactResolutionException(List<ArtifactResult> results, String message) {
053        super(message, getCause(results));
054        this.results = results != null ? results : Collections.emptyList();
055    }
056
057    /**
058     * Creates a new exception with the specified results, detail message and cause.
059     *
060     * @param results The resolution results at the point the exception occurred, may be {@code null}.
061     * @param message The detail message, may be {@code null}.
062     * @param cause The exception that caused this one, may be {@code null}.
063     */
064    public ArtifactResolutionException(List<ArtifactResult> results, String message, Throwable cause) {
065        super(message, cause);
066        this.results = results != null ? results : Collections.emptyList();
067    }
068
069    /**
070     * Gets the resolution results at the point the exception occurred. Despite being incomplete, callers might want to
071     * use these results to fail gracefully and continue their operation with whatever interim data has been gathered.
072     *
073     * @return The resolution results, never {@code null} (empty if unknown).
074     */
075    public List<ArtifactResult> getResults() {
076        return results;
077    }
078
079    /**
080     * Gets the first result from {@link #getResults()}. This is a convenience method for cases where callers know only
081     * a single result/request is involved.
082     *
083     * @return The (first) resolution result or {@code null} if none.
084     */
085    public ArtifactResult getResult() {
086        return (results != null && !results.isEmpty()) ? results.get(0) : null;
087    }
088
089    private static String getMessage(List<? extends ArtifactResult> results) {
090        if (results == null) {
091            return null;
092        }
093        StringBuilder buffer = new StringBuilder(256);
094
095        buffer.append("The following artifacts could not be resolved: ");
096
097        String sep = "";
098        for (ArtifactResult result : results) {
099            if (!result.isResolved()) {
100                buffer.append(sep);
101                buffer.append(result.getRequest().getArtifact());
102                LocalArtifactResult localResult = result.getLocalArtifactResult();
103                if (localResult != null) {
104                    buffer.append(" (");
105                    if (localResult.getFile() != null) {
106                        buffer.append("present");
107                        if (!localResult.isAvailable()) {
108                            buffer.append(", but unavailable");
109                        }
110                    } else {
111                        buffer.append("absent");
112                    }
113                    buffer.append(")");
114                }
115                sep = ", ";
116            }
117        }
118
119        Throwable cause = getCause(results);
120        if (cause != null) {
121            buffer.append(": ").append(cause.getMessage());
122        }
123
124        return buffer.toString();
125    }
126
127    /**
128     * This method tries to be smart and figure out "cause", but it results in somewhat incomplete result. Maven Core
129     * and probably many other code relies on it, so is left in place, but client code should use {@link #getResults()}
130     * and {@link ArtifactResult#getMappedExceptions()} methods to build more appropriate error messages.
131     */
132    private static Throwable getCause(List<? extends ArtifactResult> results) {
133        if (results == null) {
134            return null;
135        }
136        for (ArtifactResult result : results) {
137            if (!result.isResolved()) {
138                Throwable notFound = null, offline = null;
139                for (Throwable t : result.getExceptions()) {
140                    if (t instanceof ArtifactNotFoundException) {
141                        if (notFound == null || notFound instanceof ArtifactFilteredOutException) {
142                            notFound = t;
143                        }
144                        if (offline == null && t.getCause() instanceof RepositoryOfflineException) {
145                            offline = t;
146                        }
147                    } else {
148                        return t;
149                    }
150                }
151                if (offline != null) {
152                    return offline;
153                }
154                if (notFound != null) {
155                    return notFound;
156                }
157            }
158        }
159        return null;
160    }
161}