/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* 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:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.model.ext.maven.internal;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.commons.ComparatorUtils;
import org.eclipse.skalli.model.Issue;
import org.eclipse.skalli.model.Issuer;
import org.eclipse.skalli.model.Project;
import org.eclipse.skalli.model.Severity;
import org.eclipse.skalli.model.ValidationException;
import org.eclipse.skalli.model.ext.devinf.DevInfProjectExt;
import org.eclipse.skalli.model.ext.maven.MavenPomResolver;
import org.eclipse.skalli.model.ext.maven.MavenProjectExt;
import org.eclipse.skalli.model.ext.maven.MavenReactor;
import org.eclipse.skalli.model.ext.maven.MavenReactorProjectExt;
import org.eclipse.skalli.model.ext.maven.internal.config.MavenResolverConfig;
import org.eclipse.skalli.nexus.NexusClient;
import org.eclipse.skalli.services.Services;
import org.eclipse.skalli.services.entity.EntityServices;
import org.eclipse.skalli.services.project.ProjectService;
import org.eclipse.skalli.services.scheduler.RunnableSchedule;
import org.eclipse.skalli.services.scheduler.Schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MavenResolverRunnable extends RunnableSchedule implements Issuer {
private static final Logger LOG = LoggerFactory.getLogger(MavenResolverRunnable.class);
private String userId;
/**
* Creates a Maven resolver runnable for a single shot execution.
*
* @param userId the user on which behalf the Maven resolution is triggered.
*/
public MavenResolverRunnable(String userId) {
super(Schedule.NOW, "Maven Resolver");
if (StringUtils.isBlank(userId)) {
throw new IllegalArgumentException("Argument 'userId' must not be null or blank");
}
this.userId = userId;
}
/**
* Creates a Maven resolver runnable for scheduled execution.
*
* @param config the configuration settings for the Maven resolver.
*/
public MavenResolverRunnable(MavenResolverConfig config) {
super(config.getSchedule(), "Maven Resolver");
userId = config.getUserId();
if (StringUtils.isBlank(userId)) {
userId = MavenResolverRunnable.class.getName();
}
}
@Override
public void run() {
try {
setLastStarted(System.currentTimeMillis());
ProjectService projectService = getProjectService();
List<UUID> uuids = new ArrayList<UUID>(projectService.keySet());
run(projectService, uuids);
} catch (Exception e) {
LOG.error("MavenResolver: Failed", e);
} finally {
setLastCompleted(System.currentTimeMillis());
}
}
public void run(UUID uuid) {
ProjectService projectService = getProjectService();
List<UUID> uuids = Collections.singletonList(uuid);
run(projectService, uuids);
}
private void run(ProjectService projectService, List<UUID> uuids) {
LOG.info(MessageFormat.format("MavenResolver: Started ({0} projects to scan)", uuids.size()));
NexusClient nexusClient = getNexusClient();
NexusVersionsResolver versionsResolver = nexusClient != null?
new NexusVersionsResolver(nexusClient) : null;
int count = 0;
int countUpdated = 0;
int countInvalidPom = 0;
int countIOExceptions = 0;
int countUnexpectedException = 0;
int countPersistingProblem = 0;
SortedSet<Issue> issues = new TreeSet<Issue>();
for (UUID uuid: uuids) {
if (count > 0) {
// delay the execution for 10 seconds, otherwise we may
// overcharge the remote systems with out requests;
// but not before the first project in the loop
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
break;
}
}
++count;
Project project = projectService.getByUUID(uuid);
if (project == null) {
handleIssue(Severity.ERROR, uuid, MessageFormat.format(
"MavenResolver: Unknown project {0}", uuid),
null, issues);
continue;
}
LOG.info(MessageFormat.format("MavenResolver: Processing {0}", project.getProjectId()));
MavenReactor oldReactor = getMavenReactorProperty(project);
MavenReactor newReactor = null;
try {
newReactor = resolveProject(project, oldReactor, issues);
} catch (ValidationException e) {
++countInvalidPom;
handleIssue(Severity.WARNING, uuid, MessageFormat.format(
"MavenResolver: Invalid POM found for project {0}",
project.getProjectId()), e, issues);
continue;
} catch (IOException e) {
++countIOExceptions;
handleIssue(Severity.ERROR, uuid, MessageFormat.format(
"MavenResolver: Failed to retrieve POM for project {0}",
project.getProjectId()), e, issues);
continue;
} catch (RuntimeException e) {
++countUnexpectedException;
handleIssue(Severity.FATAL, uuid, MessageFormat.format(
"MavenResolver: Unexpected exception when resolving POMs for project {0}",
project.getProjectId()), e, issues);
continue;
}
if (versionsResolver != null) {
try {
versionsResolver.addVersions(newReactor, oldReactor);
versionsResolver.setNexusVersions(newReactor);
} catch (RuntimeException e) {
++countUnexpectedException;
handleIssue(Severity.FATAL, uuid, MessageFormat.format(
"MavenResolver: Unexpected exception when retrieving artifact versions for project {0}",
project.getProjectId()), e, issues);
continue;
}
}
if (!ComparatorUtils.equals(newReactor, oldReactor)) {
if (updateMavenReactorExtension(project, newReactor)) {
try {
projectService.persist(project, userId);
handleIssue(Severity.INFO, uuid, MessageFormat.format(
"MavenResolver: Updated project {0}",
project.getProjectId()), null, issues);
++countUpdated;
} catch (ValidationException e) {
++countPersistingProblem;
handleIssue(Severity.FATAL, uuid, MessageFormat.format(
"MavenResolver: Failed to persist project {0}",
project.getProjectId()), e, issues);
continue;
} catch (RuntimeException e) {
++countUnexpectedException;
handleIssue(Severity.FATAL, uuid, MessageFormat.format(
"MavenResolver: Unexpected exception when persisting project {0}",
project.getProjectId()), e, issues);
continue;
}
}
}
LOG.info(MessageFormat.format("MavenResolver: Processed {0} ({1} projects processed, {2} remaining)",
project.getProjectId(), count, uuids.size() - count));
}
LOG.info(MessageFormat.format(
"MavenResolver: Finished ({0} projects processed, {1} updated, {2} with invalid POM, {3} persisting problems, " +
"{4} i/o exceptions, {5} unexpected exceptions)",
uuids.size(), countUpdated, countInvalidPom, countPersistingProblem, countIOExceptions,
countUnexpectedException));
if (issues.size() > 0) {
LOG.info(Issue.getMessage("MavenResolver: Issue Summary", issues));
}
}
/**
* Returns the Maven reactor information for a given project.
*
* @param project the project to resolve.
* @param oldReactor the Maven information from a previous run.
* @param issues set of issues found during the resolving.
* @return the Maven reactor for the project, or <code>null</code> if
* <ul>
* <li>the reactor POM path ({@link MavenProjectExt#PROPERTY_REACTOR_POM}) is not specified,</li>
* <li>the SCM location ({@link DevInfProjectExt#PROPERTY_SCM_LOCATIONS}) is not defined,</li>
* <li>the mapping from the SCM location to the source repository is not configured,</li>
* <li>or the mapping is not applicable for the given SCM location.</li>
* </ul>
*
* @throws IOException if an i/o error occured, e.g. the connection to the source repository
* providing POM files cannot be established or is lost.
* @throws ValidationException if any of the relevant POMs is invalid or cannot be parsed.
*/
MavenReactor resolveProject(Project project, MavenReactor oldReactor, SortedSet<Issue> issues)
throws IOException, ValidationException {
String reactorPomPath = getReactorPomPathProperty(project);
if (reactorPomPath == null) {
if (oldReactor != null) {
handleIssue(Severity.WARNING, project.getUuid(), MessageFormat.format(
"MavenResolver: Rector POM Path property has been removed from project {0}",
project.getProjectId()), null, issues);
}
return null;
}
String scmLocation = getScmLocationProperty(project);
if (scmLocation == null) {
if (oldReactor != null) {
handleIssue(Severity.WARNING, project.getUuid(), MessageFormat.format(
"MavenResolver: SCM Location property has been removed from project {0}",
project.getProjectId()), null, issues);
}
return null;
}
MavenPomResolver pomResolver = getMavenPomResolver(scmLocation);
if (pomResolver == null) {
handleIssue(Severity.ERROR, project.getUuid(), MessageFormat.format(
"MavenResolver: No mapping to source repository available for SCM location {0} of project {1}",
scmLocation, project.getProjectId()), null, issues);
return null;
}
MavenResolver resolver = getMavenResolver(project.getUuid(), pomResolver);
return resolver.resolve(scmLocation, reactorPomPath);
}
private void handleIssue(Severity severity, UUID uuid, String message, Exception e, SortedSet<Issue> issues) {
if (e != null) {
LOG.error(message, e);
message = MessageFormat.format("{0} [{1}]", message, e.getMessage()); //$NON-NLS-1$
if (e instanceof ValidationException) {
issues.addAll(((ValidationException) e).getIssues());
}
} else {
logIssue(message, severity);
}
issues.add(new Issue(severity, MavenResolverRunnable.class, uuid, MavenReactorProjectExt.class, null, message));
}
private void logIssue(String message, Severity severity) {
switch (severity) {
case ERROR:
LOG.error(message);
break;
case FATAL:
LOG.error(message);
break;
case INFO:
LOG.info(message);
break;
case WARNING:
LOG.warn(message);
break;
default:
break;
}
}
private boolean updateMavenReactorExtension(Project project, MavenReactor mavenReactor) {
MavenReactorProjectExt ext = project.getExtension(MavenReactorProjectExt.class);
if (ext == null) {
if (mavenReactor == null) {
return false;
}
ext = new MavenReactorProjectExt();
project.addExtension(ext);
}
ext.setMavenReactor(mavenReactor);
return true;
}
@SuppressWarnings("nls")
private String getReactorPomPathProperty(Project project) {
MavenProjectExt ext = project.getExtension(MavenProjectExt.class);
if (ext == null) {
return null;
}
String reactorPomPath = ext.getReactorPOM();
if (reactorPomPath == null) {
reactorPomPath = "";
}
if (reactorPomPath.endsWith("/pom.xml")) {
reactorPomPath = reactorPomPath.substring(0, reactorPomPath.length() - 8);
}
if (reactorPomPath.startsWith("/")) {
reactorPomPath = reactorPomPath.substring(1);
}
if (reactorPomPath.endsWith("/")) {
reactorPomPath = reactorPomPath.substring(0, reactorPomPath.length() - 1);
}
return reactorPomPath;
}
private String getScmLocationProperty(Project project) {
DevInfProjectExt devExtension = project.getExtension(DevInfProjectExt.class);
if (devExtension == null) {
return null;
}
String scmLocation = devExtension.getScmLocation();
if (StringUtils.isEmpty(scmLocation)) {
return null;
}
return scmLocation;
}
private MavenReactor getMavenReactorProperty(Project project) {
MavenReactorProjectExt ext = project.getExtension(MavenReactorProjectExt.class);
return ext != null ? ext.getMavenReactor() : null;
}
// package protected for testing purposes
MavenPomResolver getMavenPomResolver(String scmLocation) {
Set<MavenPomResolver> mavenPomResolvers = Services.getServices(MavenPomResolver.class);
if (mavenPomResolvers.isEmpty()) {
LOG.debug("no " + MavenPomResolver.class.getName() + " configuration");
return null;
}
for (MavenPomResolver mavenPomResolver : mavenPomResolvers) {
if (mavenPomResolver.canResolve(scmLocation)) {
return mavenPomResolver;
}
}
return null;
}
// package protected for testing purposes
MavenResolver getMavenResolver(UUID entityId, MavenPomResolver mavenPomResolver) {
return new MavenResolver(entityId, mavenPomResolver);
}
// package protected for testing purposes
ProjectService getProjectService() {
return ((ProjectService)EntityServices.getByEntityClass(Project.class));
}
// package protected for testing purposes
NexusClient getNexusClient() {
return Services.getService(NexusClient.class);
}
}