All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.maven.problems.SanityBuildAction Maven / Gradle / Ivy

The newest version!
/*
 * 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.netbeans.modules.maven.problems;

import java.util.Arrays;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.TestChecker;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.execute.ExecutionContext;
import org.netbeans.modules.maven.api.execute.ExecutionResultChecker;
import org.netbeans.modules.maven.api.execute.RunConfig;
import org.netbeans.modules.maven.api.execute.RunConfig.ReactorStyle;
import org.netbeans.modules.maven.api.execute.RunUtils;
import org.netbeans.modules.maven.execute.BeanRunConfig;
import org.netbeans.modules.maven.execute.MavenProxySupport;
import org.netbeans.modules.maven.execute.MavenProxySupport.ProxyResult;
import org.netbeans.modules.maven.options.MavenSettings;
import static org.netbeans.modules.maven.problems.Bundle.*;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.netbeans.spi.project.ui.ProjectProblemResolver;
import org.netbeans.spi.project.ui.ProjectProblemsProvider;
import org.openide.execution.ExecutorTask;
import org.openide.filesystems.FileUtil;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;

/**
 * Corrective action to run some target which can download plugins or parent POMs.
 * At worst it will show the same problem in the Output Window, so the user is more likely
 * to believe that there really is a problem with their project, not NetBeans.
 */
@Messages({"ACT_validate=Priming Build",
            "ACT_PrimingComplete=Priming build was completed.",
            "ACT_PrimingFailed=Priming build failed. Please check project build output and resolve problems manually.",
            "ACT_start_validate=Priming build was started."})
public class SanityBuildAction implements ProjectProblemResolver {
    private static final Logger LOG = Logger.getLogger(SanityBuildAction.class.getName());
    
    private final Project nbproject;
    private final Supplier checkSupplier;
    
    /**
     * The priming build, which is currently pending or recently completed.
     * Initially empty.
     */
    private volatile CompletableFuture pendingResult;

    public SanityBuildAction(Project nbproject, Supplier checkSupplier) {
        this.nbproject = nbproject;
        this.checkSupplier = checkSupplier;
    }

    public SanityBuildAction(Project nbproject, Supplier checkSupplier, Future otherResult) {
        this.nbproject = nbproject;
        this.checkSupplier = checkSupplier;
    }
    
    public Future getPendingResult() {
        return this.pendingResult;
    }

    @NbBundle.Messages({
        "ERR_SanityBuildCancalled=Sanity build cancelled",
        "# {0} - message",
        "ERR_ProxyUpdateFailed=Proxy setup failed: {0}"
    })
    @Override
    public CompletableFuture resolve() {
        return resolve(null);
    }
    
    public CompletableFuture resolve(Lookup context) {
        CompletableFuture pr = pendingResult;
        if (pr != null && !pr.isDone()) {
            LOG.log(Level.FINE, "SanityBuild.resolve returns: {0}", pendingResult);
            return pendingResult;
        }
        final CompletableFuture publicResult = new CompletableFuture<>();
        AtomicReference primingResult = new AtomicReference<>();
        AtomicReference primingException = new AtomicReference<>();
        Runnable toRet = new Runnable() {
            @Override
            public void run() {
                /**
                 * From the appearance of the Sanity action, the project's state may have changed. In that case the sanity build should not run maven again.
                 */
                if (!checkSupplier.get()) {
                    ProjectProblemsProvider.Result r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED, ACT_start_validate());
                    if (r != null) {
                        LOG.log(Level.FINE, "Project {0} is OK before sanity build action.", nbproject);
                        publicResult.complete(r);
                        return;
                    }
                }
                try {
                    LOG.log(Level.FINE, "Configuring sanity build");
                    AtomicInteger result = new AtomicInteger();
                    BeanRunConfig config = new BeanRunConfig();
                    if (context != null) {
                        config.setActionContext(context);
                    }
                    config.setExecutionDirectory(FileUtil.toFile(nbproject.getProjectDirectory()));
                    NbMavenProject mavenPrj = nbproject.getLookup().lookup(NbMavenProject.class);
                    MavenProject loaded = NbMavenProject.getPartialProject(mavenPrj.getMavenProject());
                    String goals;
                    if (mavenPrj != null
                            && mavenPrj.getMavenProject().getVersion() != null 
                            && mavenPrj.getMavenProject().getVersion().endsWith("SNAPSHOT")) {
                        goals = NbPreferences.forModule(MavenSettings.class).get("primingBuild.snapshot.goals", "install");
                    } else {
                        goals = NbPreferences.forModule(MavenSettings.class).get("primingBuild.regular.goals", "package");
                    }
                    config.setGoals(Arrays.asList("--fail-at-end", goals)); // NOI18N
                    config.setReactorStyle(ReactorStyle.ALSO_MAKE);
                    config.setProperty(TestChecker.PROP_SKIP_TEST, "true"); //priming doesn't need test execution, just compilation
                    config.setProject(nbproject);
                    String label = build_label(nbproject.getProjectDirectory().getNameExt());
                    config.setExecutionName(label);
                    config.setTaskDisplayName(label);
                    config.setInternalProperty(SanityBuildAction.class.getName(), result);
                    
                    MavenProxySupport mps = nbproject.getLookup().lookup(MavenProxySupport.class);
                    if (mps != null) {
                        ProxyResult res;
                        try {
                            res = mps.checkProxySettings().get();
                            if (res.getStatus() == MavenProxySupport.Status.ABORT) {
                                ProjectProblemsProvider.Result r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED, ERR_SanityBuildCancalled());
                                primingResult.set(r);
                                return;
                            }
                        } catch (ExecutionException ex) {
                            ProjectProblemsProvider.Result r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED, ERR_ProxyUpdateFailed(ex.getLocalizedMessage()));
                            primingResult.set(r);
                            return;
                        } catch (InterruptedException ex) {
                            ProjectProblemsProvider.Result r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED, ERR_SanityBuildCancalled());
                            primingResult.set(r);
                            return;
                        }
                    }
                    long t = System.currentTimeMillis();
                    LOG.log(Level.FINE, "Executing sanity build: goals = {0}, properties = {1}", new Object[] { config.getGoals(), config.getProperties() });
                    ExecutorTask et = RunUtils.run(config);
                    AtomicBoolean stopped = new AtomicBoolean();
                    if (et != null) {
                        publicResult.exceptionally(x -> {
                            if (x instanceof CancellationException) {
                                stopped.set(true);
                                et.stop();
                            }
                            return null;
                        });
                        et.waitFinished();
                        ProjectProblemsProvider.Result r;
                        if (!stopped.get() && (result.get() == 0 ||
                            // if the build failed, the problem may be in user's sources, rather than in
                            // missing artifacts. Check if sanity build is still needed:
                            !nbproject.getLookup().lookup(SanityBuildNeededChecker.class).isSanityBuildNeeded())) {
                            r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED, ACT_PrimingComplete());
                        } else {
                            r = ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED, ACT_PrimingFailed());
                        }
                        LOG.log(Level.FINE, "Sanity build of {0} finished, took {1} ms.", new Object[] { nbproject, System.currentTimeMillis() - t});
                        primingResult.set(r);
                    }
                } catch (RuntimeException | Error e) {
                    // always report completness, otherwise tasks that wait on priming build could block indefinitely.
                    LOG.log(Level.FINE, "Sanity build failed", e);
                    primingException.set(e);
                    throw e;
                }
            }
        };
        synchronized (this) {
            if (pendingResult != pr) {
                // someone has started or completed the pending result before us: back off, use the
                // existing one.
                return pendingResult;
            }
            pendingResult = publicResult;
        }
        // completing the exception ONLY after the Task completes ensures that all events were delivered.
        ((NbMavenProjectImpl)this.nbproject).scheduleProjectOperation(MavenModelProblemsProvider.RP, toRet, 0, true).addTaskListener((l) -> {
            if (primingException.get() != null) {
                publicResult.completeExceptionally(primingException.get());
            } else {
                publicResult.complete(primingResult.get());
            }
        });
        return publicResult;
    }
    
    @Override
    public int hashCode() {
        int hash = SanityBuildAction.class.hashCode();
        hash = 67 * hash + (this.nbproject != null ? this.nbproject.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final SanityBuildAction other = (SanityBuildAction) obj;
        if (this.nbproject != other.nbproject && (this.nbproject == null || !this.nbproject.equals(other.nbproject))) {
            return false;
        }
        return true;
    }


    @ProjectServiceProvider(service=ExecutionResultChecker.class, projectType="org-netbeans-modules-maven")
    public static class ResultChecker implements ExecutionResultChecker {

        @Override
        public void executionResult(RunConfig config, ExecutionContext res, int resultCode) {
            Object resultObj = config.getInternalProperties().get(SanityBuildAction.class.getName());
            if (!(resultObj instanceof AtomicInteger)) {
                return ;
            }
            AtomicInteger result = (AtomicInteger) resultObj;
            result.set(resultCode);
        }
    }

    public interface SanityBuildNeededChecker {
        public boolean isSanityBuildNeeded();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy