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

org.netbeans.modules.maven.queries.MavenReloadImplementation 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.queries;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.modelcache.MavenProjectCache;
import org.netbeans.modules.project.dependency.ProjectReload;
import org.netbeans.modules.project.dependency.ProjectReload.Quality;
import org.netbeans.modules.project.dependency.ProjectReload.StateRequest;
import org.netbeans.modules.project.dependency.spi.ProjectReloadImplementation;
import org.netbeans.spi.project.LookupProvider;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;

/**
 * Basic maven implementation. It uses Maven internal API to load or force-reload the project to
 * get MavenProject instance. Hooks to PROP_PROJECT change event and invalidates last-known state.
 * @author sdedic
 */
@ProjectServiceProvider(service = ProjectReloadImplementation.class, projectTypes = {
    @LookupProvider.Registration.ProjectType(id = NbMavenProject.TYPE, position = 10000)
    
})
public class MavenReloadImplementation implements ProjectReloadImplementation, PropertyChangeListener {
    private static final Logger LOG = Logger.getLogger(MavenReloadImplementation.class.getName());
    
    private final Project project;
    private volatile Reference lastData = new WeakReference<>(null);

    
    public MavenReloadImplementation(Project project) {
        this.project = project;
        NbMavenProject.addPropertyChangeListener(project, 
                WeakListeners.propertyChange(this, project)
        );
    }

    @Override
    public void projectDataReleased(ProjectStateData data) {
        if (lastData.get() == data) {
            lastData.clear();
        }
    }
    
    // test only
    ProjectStateData getLastCachedData() {
        return lastData.get();
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName() == null) {
            return;
        }
        ProjectStateData st = lastData.get();
        if (st == null) {
            return;
        }
        if (NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
            st.fireChanged(true, false);
        }
    }
    
    /**
     * Returns project metadata files. If 'forReload' is false, it just returns project's
     * pom.xml file. If the 'forReload' is true, it will return all the settings and 
     * referenced parent project's files.
     * 
     * @param forReload
     * @return 
     */
    public Set findProjectFiles() {
        NbMavenProject p = project.getLookup().lookup(NbMavenProject.class);
        return findProjectFiles0(p.getMavenProject());
    }

    Set findProjectFiles0(MavenProject mp) {
        File pomFile = mp.getFile();
        Set fileSet = new HashSet<>();
        fileSet.add(FileUtil.toFileObject(pomFile));
        
        MavenExecutionRequest rq = EmbedderFactory.getProjectEmbedder().createMavenExecutionRequest();
        File userSettings = rq.getUserSettingsFile();
        File toolchains = rq.getGlobalToolchainsFile();
        File globalSettings = rq.getGlobalSettingsFile();
        if (userSettings != null && userSettings.exists()) {
            fileSet.add(FileUtil.toFileObject(userSettings));
        }
        if (toolchains != null && toolchains.exists()) {
            fileSet.add(FileUtil.toFileObject(toolchains));
        }
        if (globalSettings != null && globalSettings.exists()) {
            fileSet.add(FileUtil.toFileObject(globalSettings));
        }
        
        Set processed = new HashSet<>();
        ArrayDeque toProcess = new ArrayDeque<>();
        Artifact a = mp.getParentArtifact();
        if (a != null) {
            toProcess.add(a);
        }
        toProcess.addAll(mp.getArtifacts());
        
        while ((a = toProcess.poll()) != null) {
            if (!processed.add(a)) {
                continue;
            }
            File pom = MavenFileOwnerQueryImpl.getInstance().getOwnerPOM(a.getGroupId(), a.getArtifactId(), a.getVersion());
            if (pom != null) {
                Project parentOwner = FileOwnerQuery.getOwner(FileUtil.toFileObject(pom));
                // can't call getProjectState, since that may block 
                FileObject parentPom = parentOwner.getProjectDirectory().getFileObject("pom.xml");
                if (parentPom != null) {
                    fileSet.add(parentPom);
                }
                NbMavenProject p2 = project.getLookup().lookup(NbMavenProject.class);
                a = p2.getMavenProject().getParentArtifact();
                if (a != null) {
                    toProcess.add(a);
                }
                toProcess.addAll(p2.getMavenProject().getArtifacts());
            }
        }
        return fileSet;
    }
    
    private static class CF extends CompletableFuture> {}

    static ProjectReload.Quality getProjectQuality(MavenProject mp) {
        if (mp == null) {
            return ProjectReload.Quality.NONE;
        }
        if (MavenProjectCache.isFallbackproject(mp)) {
            // could not load at all.
            MavenProject fallback = MavenProjectCache.getPartialProject(mp);
            return fallback != null ? 
                    ProjectReload.Quality.BROKEN:
                    ProjectReload.Quality.FALLBACK;
        }
        if (!MavenProjectCache.getPlaceholderArtifacts(mp).isEmpty()) {
            return ProjectReload.Quality.LOADED;
        }
        return ProjectReload.Quality.RESOLVED;
    }
    
    private ProjectStateData createStateData(MavenProject mp, Quality q) {
        ProjectStateData d;
        
        if (q == null) {
            q = getProjectQuality(mp);
        }
        
        ProjectStateBuilder builder = ProjectStateData.builder(q).
                files(findProjectFiles()).
                timestamp(MavenProjectCache.getLoadTimestamp(mp));
        builder.data(mp);
        builder.attachLookup(Lookups.fixed(mp));
        d = builder.build();
        synchronized (this) {
            ProjectStateData d2 = lastData.get();
            if (d2 != null) {
                if (d2.getProjectData() == mp) {
                    return d2;
                }
                d2.fireChanged(true, false);
            }
            lastData = new WeakReference<>(d);
        }
        return d;
    }

    @NbBundle.Messages({
        "# {0} - project name",
        "ERR_UnprimedInOfflineMode=Priming build for {0} is required, but offline operation was requested.",
        "# {0} - project name",
        "# {1} - parent gav",
        "ERR_ParentPomMissing=Project {0} is missing its parent artifact ({1}) is missing and offline operation was requested"
    })
    @Override
    public CompletableFuture reload(Project project, StateRequest request, LoadContext context) {
        CF cf = new CF();
        loadMavenProject(request, cf);
        return cf;
    }

    private void loadMavenProject(StateRequest request, CF future) {
        NbMavenProjectImpl nbImpl = project.getLookup().lookup(NbMavenProjectImpl.class);
        if (!request.isForceReload()) {
            if (request.isConsistent()) {
                nbImpl.getFreshOriginalMavenProject().thenAccept((mp) ->loadMavenProject2(mp, request, future));
            } else {
                MavenProject mp = nbImpl.getOriginalMavenProjectOrNull();
                if (mp != null) {
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.log(Level.FINE, "Got already loaded Maven project {0}@{1}", new Object[] { mp, Integer.toHexString(System.identityHashCode(mp)) });
                    }
                    loadMavenProject2(mp, request, future);
                }
            }
            return;
        }
        loadMavenProject3(future);
    }
    
    private void loadMavenProject2(MavenProject p, StateRequest request, CF future) {
        if (p != null) {
            ProjectReload.Quality q = getProjectQuality(p);
            long ts = MavenProjectCache.getLoadTimestamp(p);
            LOG.log(Level.FINE, "Got Maven Project {0}@{1}, with quality {2}, timestamp {3}", new Object[] { p, Integer.toHexString(System.identityHashCode(p)), q, ts });
            if (q.isAtLeast(request.getMinQuality())) {
                if (!request.isConsistent()) {
                    future.complete(createStateData(p, null));
                    return;
                }

                // check that the project is consistent with known files:
                Collection fos = findProjectFiles0(p);
                boolean obsolete = false;
                for (FileObject f : fos) {
                    if (f.lastModified().getTime() > ts) {
                        LOG.log(Level.FINE, "Maven Project {0}@{1} is obsolete because of {2}, file stamp: {3}", new Object[] { p, Integer.toHexString(System.identityHashCode(p)),  f, f.lastModified().getTime() });
                        obsolete = true;
                    }
                }
                if (!obsolete) {
                    future.complete(createStateData(p, null));
                    return;
                }
            }
        }
        loadMavenProject3(future);
    }
        
    private void loadMavenProject3(CF future) {
        NbMavenProjectImpl nbImpl = project.getLookup().lookup(NbMavenProjectImpl.class);
        MavenProject current = nbImpl.getOriginalMavenProjectOrNull();
        RequestProcessor.Task t = nbImpl.fireProjectReload(true);
        LOG.log(Level.FINE, "Scheduled project reload: {0}", t);
        t.addTaskListener(new TaskListener() {
            @Override
            public void taskFinished(Task task) {
                task.removeTaskListener(this);
                // retain initial = true for the first project load.
                nbImpl.getFreshOriginalMavenProject().thenAccept((mp) -> {
                    if (LOG.isLoggable(Level.FINE)) {
                        ProjectReload.Quality q = getProjectQuality(mp);
                        long ts = MavenProjectCache.getLoadTimestamp(mp);
                        LOG.log(Level.FINE, "Reloaded Maven project {0}@{1}, with quality {2}, timestamp {3}", new Object[] { mp, Integer.toHexString(System.identityHashCode(mp)), q, ts });
                    }
                    future.complete(createStateData(mp, null));
                }).exceptionally(ex -> {
                    MavenProject renewed = nbImpl.getOriginalMavenProjectOrNull();
                    LOG.log(Level.FINE, "Failed to load maven project, got {0}, previous {1}", new Object[] { renewed, current });
                    if (renewed == current) {
                        ProjectStateData sd = createStateData(current, Quality.BROKEN);
                        sd.fireChanged(false, true);
                        future.complete(sd);
                    } else {
                        future.complete(createStateData(renewed, Quality.BROKEN));
                    }
                    return null;
                });
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy