/******************************************************************************* * * Copyright (c) 2010-2011 Sonatype, Inc, Oracle Corporation * * 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: * * Anton Kozak. * * *******************************************************************************/ package org.hudsonci.maven.plugin.builder; import org.hudsonci.maven.plugin.builder.internal.PerformBuild; import org.hudsonci.maven.plugin.documents.DocumentNotFoundException; import org.hudsonci.maven.plugin.install.MavenInstallation; import org.hudsonci.service.NodeService; import org.hudsonci.service.SecurityService; import org.hudsonci.utils.plugin.ui.JellyAccessible; import org.hudsonci.maven.model.config.BuildConfigurationDTO; import org.hudsonci.maven.model.config.DocumentDTO; import org.hudsonci.maven.model.state.BuildStateDTO; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamOmitField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.Build; import hudson.model.BuildListener; import hudson.security.ACL; import hudson.tasks.Builder; import javax.inject.Inject; import java.io.IOException; import java.util.concurrent.Callable; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; /** * Hudson Maven {@link Builder}. * * WARNING: for {@link Build}s (which includes multi-configuration builds) the * same {@link Builder} instance is used for all executions. Hence do not store state * in the builder if it's specific to an execution. Instead use the {@link BuildStateRecord} * or {@link BuildStateDTO} created in {@link #perform(AbstractBuild, Launcher, BuildListener)} * by passing it to the necessary objects. In the future we may use a ThreadLocal * or some other context during the life of a build execution. * * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @since 2.1.0 */ @XStreamAlias("maven-builder") public class MavenBuilder extends Builder { private static final Logger log = LoggerFactory.getLogger(MavenBuilder.class); private final BuildConfigurationDTO config; @XStreamOmitField private SecurityService security; @XStreamOmitField private NodeService nodes; public MavenBuilder(final BuildConfigurationDTO config) { this.config = checkNotNull(config); } @Inject public void setSecurity(final SecurityService security) { this.security = checkNotNull(security); } private SecurityService getSecurity() { checkState(security != null); return security; } @Inject public void setNodes(final NodeService nodes) { this.nodes = checkNotNull(nodes); } public NodeService getNodes() { checkState(nodes != null); return nodes; } @Override public MavenBuilderDescriptor getDescriptor() { return (MavenBuilderDescriptor) super.getDescriptor(); } @JellyAccessible public BuildConfigurationDTO getConfig() { return config; } public MavenInstallation getMavenInstallation() { String installationId = getConfig().getInstallationId(); if (installationId != null && !"NONE".equals(installationId)) { for (MavenInstallation installation : getDescriptor().getInstallations()) { if (installationId.equals(installation.getName())) { return installation; } } } return null; } public DocumentDTO getDocument(final String id) { // WORK AROUND: Handle special case from Jelly configuration, "NONE" == null ATM. // WORK AROUND: ... When configured via REST and we use null for none then drop this check (keeping null check only) if (id == null || "NONE".equals(id)) { return null; } log.debug("Getting document for ID: {}", id); DocumentDTO document = null; try { // Need to run as SYSTEM when fetching the documents to be used document = getSecurity().callAs2(ACL.SYSTEM, new Callable<DocumentDTO>() { public DocumentDTO call() { return getDescriptor().getDocuments().getDocument(id, false); } }); log.debug("Document: {}", document); } catch (DocumentNotFoundException e) { log.warn("Ignoring missing document for ID: {}", id); } return document; } @Override public boolean perform(final AbstractBuild<?,?> build, final Launcher launcher, final BuildListener listener) throws InterruptedException, IOException { BuildStateDTO state = attachBuildState( build ).getState(); attachBuildAction( build ); // Attach the build configuration to the state state.setBuildConfiguration(getConfig()); // Perform the build boolean result = new PerformBuild(this, state, build, launcher, listener).execute(); // TODO: free up the BuildStateDTO hard-ref once the build has saved to disk, so that the xref soft-reference // (when configured) will allow it to be reclaimed when running low on memory. // Refer to change history for the removed BuildStateClearer. return result; } /** * Attach the build state record and prime the build state instance. * @return the record attached to the build */ private BuildStateRecord attachBuildState( final AbstractBuild<?, ?> build ) { BuildStateRecord record = new BuildStateRecord(build); build.addAction(record); return record; } /** * Attach the build action if its not already attached */ private void attachBuildAction( final AbstractBuild<?, ?> build ) { if (build.getAction(MavenBuildAction.class) == null) { build.addAction(new MavenBuildAction(build)); } } /** * @inheritDoc */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MavenBuilder that = (MavenBuilder) o; if (!config.equals(that.config)) { return false; } return true; } /** * @inheritDoc */ @Override public int hashCode() { return config.hashCode(); } }