/******************************************************************************* * Copyright (c) 2008-2010 Sonatype, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sonatype, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.core.internal.markers; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Throwables; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.osgi.util.NLS; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.model.building.ModelBuildingException; import org.apache.maven.model.building.ModelProblem; import org.apache.maven.model.building.ModelProblem.Severity; import org.apache.maven.project.DependencyResolutionResult; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuildingException; import org.eclipse.m2e.core.embedder.IMavenConfiguration; import org.eclipse.m2e.core.internal.Messages; public class MavenMarkerManager implements IMavenMarkerManager { private static Logger log = LoggerFactory.getLogger(MavenMarkerManager.class); private final IMavenConfiguration mavenConfiguration; public MavenMarkerManager(IMavenConfiguration mavenConfiguration) { this.mavenConfiguration = mavenConfiguration; } @Override public void addMarkers(IResource pomResource, String type, MavenExecutionResult result) { SourceLocation defaultSourceLocation = new SourceLocation(1, 0, 0); List<MavenProblemInfo> allProblems = new ArrayList<MavenProblemInfo>(); allProblems.addAll(toMavenProblemInfos(pomResource, defaultSourceLocation, result.getExceptions())); MavenProject mavenProject = result.getProject(); DependencyResolutionResult resolutionResult = result.getDependencyResolutionResult(); if(resolutionResult != null) { allProblems .addAll(toMavenProblemInfos(pomResource, defaultSourceLocation, resolutionResult.getCollectionErrors())); for(org.eclipse.aether.graph.Dependency dependency : resolutionResult.getUnresolvedDependencies()) { List<Exception> exceptions = resolutionResult.getResolutionErrors(dependency); if(exceptions != null && exceptions.size() > 0) { SourceLocation sourceLocation = SourceLocationHelper.findLocation(mavenProject, dependency); allProblems.addAll(toMavenProblemInfos(pomResource, sourceLocation, exceptions)); } } } if(mavenProject != null) { addMissingArtifactProblemInfos(mavenProject, defaultSourceLocation, allProblems); } addErrorMarkers(pomResource, type, allProblems); } @Override public IMarker addMarker(IResource resource, String type, String message, int lineNumber, int severity) { return addMarker(resource, type, message, lineNumber, severity, false /*isTransient*/); } private IMarker addMarker(IResource resource, String type, String message, int lineNumber, int severity, boolean isTransient) { IMarker marker = null; try { if(resource.isAccessible()) { if(lineNumber == -1) { lineNumber = 1; } //mkleint: this strongly smells like some sort of workaround for a problem with bad marker cleanup. //adding is adding and as such shall always be performed. marker = findMarker(resource, type, message, lineNumber, severity, isTransient); if(marker != null) { // This marker already exists return marker; } marker = resource.createMarker(type); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); marker.setAttribute(IMarker.TRANSIENT, isTransient); marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); log.debug("Created marker '{}' on resource '{}'.", message, resource.getFullPath()); } } catch(CoreException ex) { log.error("Unable to add marker; " + ex.toString(), ex); //$NON-NLS-1$ } return marker; } private static <T> boolean eq(T a, T b) { if(a == null) { if(b == null) { return true; } return false; } return a.equals(b); } private IMarker findMarker(IResource resource, String type, String message, int lineNumber, int severity, boolean isTransient) throws CoreException { IMarker[] markers = resource.findMarkers(type, false /*includeSubtypes*/, IResource.DEPTH_ZERO); if(markers == null || markers.length == 0) { return null; } for(IMarker marker : markers) { if(eq(message, marker.getAttribute(IMarker.MESSAGE)) && eq(lineNumber, marker.getAttribute(IMarker.LINE_NUMBER)) && eq(severity, marker.getAttribute(IMarker.SEVERITY)) && eq(isTransient, marker.getAttribute(IMarker.TRANSIENT))) { return marker; } } return null; } private String getArtifactId(AbstractArtifactResolutionException rex) { String id = rex.getGroupId() + ":" + rex.getArtifactId() + ":" + rex.getVersion(); //$NON-NLS-1$ //$NON-NLS-2$ if(rex.getClassifier() != null) { id += ":" + rex.getClassifier(); //$NON-NLS-1$ } if(rex.getType() != null) { id += ":" + rex.getType(); //$NON-NLS-1$ } return id; } private String getRootErrorMessage(Throwable ex) { return getRootCause(ex).getMessage(); } private String getErrorMessage(Throwable ex) { StringBuilder message = new StringBuilder(); if(ex.getMessage() != null) { message.append(ex.getMessage()).append("\n\n"); } message.append(Throwables.getStackTraceAsString(ex)); return message.toString(); } private Throwable getRootCause(Throwable ex) { Throwable lastCause = ex; Throwable cause = lastCause.getCause(); while(cause != null && cause != lastCause) { if(cause instanceof ArtifactNotFoundException) { cause = null; } else { lastCause = cause; cause = cause.getCause(); } } return cause == null ? lastCause : cause; } private List<MavenProblemInfo> toMavenProblemInfos(IResource pomResource, SourceLocation location, List<? extends Throwable> exceptions) { List<MavenProblemInfo> result = new ArrayList<MavenProblemInfo>(); if(exceptions == null) { return result; } for(Throwable ex : exceptions) { if(ex instanceof org.eclipse.aether.transfer.ArtifactNotFoundException) { org.eclipse.aether.transfer.ArtifactNotFoundException artifactNotFoundException = (org.eclipse.aether.transfer.ArtifactNotFoundException) ex; ArtifactNotFoundProblemInfo problem = new ArtifactNotFoundProblemInfo(artifactNotFoundException.getArtifact(), mavenConfiguration.isOffline(), location); result.add(problem); } else if(ex instanceof AbstractArtifactResolutionException) { AbstractArtifactResolutionException abstractArtifactResolutionException = (AbstractArtifactResolutionException) ex; String errorMessage = getArtifactId(abstractArtifactResolutionException) + " " + getRootErrorMessage(ex); //$NON-NLS-1$ result.add(new MavenProblemInfo(errorMessage, location)); } else if(ex instanceof ProjectBuildingException) { Throwable cause = ex.getCause(); if(cause instanceof ModelBuildingException) { ModelBuildingException mbe = (ModelBuildingException) cause; for(ModelProblem problem : mbe.getProblems()) { String message = NLS.bind(Messages.pluginMarkerBuildError, problem.getMessage()); int severity = (Severity.WARNING == problem.getSeverity()) ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR; SourceLocation problemLocation = SourceLocationHelper.findLocation(pomResource, problem); result.add(new MavenProblemInfo(message, severity, problemLocation)); } } else { result.add(new MavenProblemInfo(getErrorMessage(ex), location)); } } else { result.add(new MavenProblemInfo(getErrorMessage(ex), location)); } } return result; } @Override public void deleteMarkers(IResource resource, String type) throws CoreException { deleteMarkers(resource, true /*includeSubtypes*/, type); } @Override public void deleteMarkers(IResource resource, boolean includeSubtypes, String type) throws CoreException { if(resource != null && resource.exists()) { resource.deleteMarkers(type, includeSubtypes, IResource.DEPTH_INFINITE); } } @Override public void deleteMarkers(IResource resource, String type, String attrName, String attrValue) throws CoreException { if(resource == null || !resource.exists()) { return; } IMarker[] markers = resource.findMarkers(type, false /*includeSubtypes*/, IResource.DEPTH_ZERO); for(IMarker marker : markers) { if(eq(attrValue, marker.getAttribute(attrName))) { marker.delete(); } } } private void addMissingArtifactProblemInfos(MavenProject mavenProject, SourceLocation location, List<MavenProblemInfo> knownProblems) { Set<Artifact> artifacts = mavenProject.getArtifacts(); all_artifacts_loop: for(Artifact mavenArtifact : artifacts) { if(!mavenArtifact.isResolved()) { org.eclipse.aether.artifact.Artifact artifact = RepositoryUtils.toArtifact(mavenArtifact); for(MavenProblemInfo problem : knownProblems) { if(problem instanceof ArtifactNotFoundProblemInfo) { ArtifactNotFoundProblemInfo artifactNotFoundProblemInfo = (ArtifactNotFoundProblemInfo) problem; if(equals(artifactNotFoundProblemInfo.getArtifact(), artifact)) { continue all_artifacts_loop; } } } knownProblems.add(new ArtifactNotFoundProblemInfo(artifact, mavenConfiguration.isOffline(), location)); } } } @Override public void addErrorMarkers(IResource resource, String type, Throwable ex) { Throwable cause = getRootCause(ex); if(cause instanceof CoreException) { CoreException cex = (CoreException) cause; IStatus status = cex.getStatus(); if(status != null) { addMarker(resource, type, status.getMessage(), 1, IMarker.SEVERITY_ERROR, false /*isTransient*/); //$NON-NLS-1$ IStatus[] children = status.getChildren(); if(children != null) { for(IStatus childStatus : children) { addMarker(resource, type, childStatus.getMessage(), 1, IMarker.SEVERITY_ERROR, false /*isTransient*/); //$NON-NLS-1$ } } } } else { addMarker(resource, type, cause.getMessage(), 1, IMarker.SEVERITY_ERROR, false /*isTransient*/); //$NON-NLS-1$ } } @Override public void addErrorMarkers(IResource resource, String type, Exception ex) { addErrorMarkers(resource, type, (Throwable) ex); } @Override public void addErrorMarkers(IResource resource, String type, List<MavenProblemInfo> problems) { for(MavenProblemInfo problem : problems) { addErrorMarker(resource, type, problem); } } @Override public void addErrorMarker(IResource resource, String type, MavenProblemInfo problem) { IMarker marker = addMarker(resource, type, problem.getMessage(), problem.getLocation().getLineNumber(), problem.getSeverity()); if(marker == null) { //resource is no longer accessible (eg. project being closed) return; } try { problem.processMarker(marker); } catch(CoreException ex) { log.error(ex.getMessage(), ex); } MarkerUtils.decorateMarker(marker); } private static boolean equals(org.eclipse.aether.artifact.Artifact a1, org.eclipse.aether.artifact.Artifact a2) { if(a1 == a2) { return true; } return a1.getArtifactId().equals(a2.getArtifactId()) && a1.getGroupId().equals(a2.getGroupId()) && a1.getVersion().equals(a2.getVersion()) && a1.getExtension().equals(a2.getExtension()) && a1.getClassifier().equals(a2.getClassifier()); } }