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

org.netbeans.modules.maven.configurations.M2ConfigProvider 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.configurations;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.codehaus.plexus.util.StringUtils;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.ProjectProfileHandler;
import org.netbeans.modules.maven.api.customizer.ModelHandle2;
import static org.netbeans.modules.maven.configurations.ConfigurationPersistenceUtils.*;
import org.netbeans.modules.maven.customizer.CustomizerProviderImpl;
import org.netbeans.modules.maven.execute.ActionNameProvider;
import org.netbeans.modules.maven.execute.DefaultActionGoalProvider;
import org.netbeans.modules.maven.execute.model.ActionToGoalMapping;
import org.netbeans.modules.maven.execute.model.NetbeansActionMapping;
import org.netbeans.modules.maven.execute.model.NetbeansActionProfile;
import org.netbeans.modules.maven.spi.actions.AbstractMavenActionsProvider;
import org.netbeans.modules.maven.spi.actions.MavenActionsProvider;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.spi.project.ProjectConfigurationProvider;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
 * WARNING: this class shall in no way use project.getLookup() as it's called
 * in the critical loop (getOriginalMavenproject
 * @author mkleint
 */
public class M2ConfigProvider implements ProjectConfigurationProvider {

    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
    private final NbMavenProjectImpl project;
    private final M2Configuration DEFAULT;
    
    // next five guarded by this
    private SortedSet provided = null;
    private SortedSet profiles = null;
    private SortedSet shared = null;
    private SortedSet nonshared = null;
    private M2Configuration active;
    private String initialActive;
    
    /**
     * Allows a temporary configuration override for a specific execution.
     */
    private final ThreadLocal activeOverride = new ThreadLocal<>();
    private final AtomicBoolean initialActiveLoaded = new AtomicBoolean(false);
    private final AuxiliaryConfiguration aux;
    private final ProjectProfileHandler profileHandler;
    private final PropertyChangeListener propertyChange;


    private static final RequestProcessor RP = new RequestProcessor(M2ConfigProvider.class.getName(),10);
    
    public M2ConfigProvider(NbMavenProjectImpl proj, AuxiliaryConfiguration aux, ProjectProfileHandler prof) {
        project = proj;
        this.aux = aux;
        profileHandler = prof;
        DEFAULT = M2Configuration.createDefault(project.getProjectDirectory());           
        active = DEFAULT;
        propertyChange = new PropertyChangeListener() {
            public @Override void propertyChange(PropertyChangeEvent evt) {
                if (NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
                    flush();
                }
            }
        };
    }
    
    private void flush() {
        synchronized (M2ConfigProvider.this) {
            provided = null;
            profiles = null;
            shared = null;
            nonshared = null;
            initialActive = active != null ? active.getId() : null; //#241337
        }
        RP.post(new Runnable() {
            public @Override void run() {
                checkActiveAgainstAll(getConfigurations(), false);
                firePropertyChange();
            }

        });
    }

    public M2Configuration setLocalConfiguration(M2Configuration cfg) {
        M2Configuration old = activeOverride.get();
        activeOverride.set(cfg);
        return old;
    }
    
    private String getInitialActive() {
        if (initialActiveLoaded.compareAndSet(false, true)) {
            initialActive = readActiveConfigurationName(aux); 
        }
        return initialActive;
    }
    
    private ThreadLocal inConfigInit = new ThreadLocal<>();
    
    private void checkActiveAgainstAll(Collection confs, boolean async) {
        Boolean b = inConfigInit.get();
        assert b != null || !Thread.holdsLock(this);
        boolean found = false;
        String id;
        synchronized (this) {
            id = internalActive().getId();
        }
        for (M2Configuration conf : confs) {
            if (conf.getId().equals(id)) {
                found = true;
                break;
            }
        }
        if (!found) {
            Runnable dothis = new Runnable() {
                    public @Override void run() {
                        M2Configuration _active;
                        synchronized (M2ConfigProvider.this) {
                            _active = internalActive();
                        }
                        try {
                            doSetActiveConfiguration(DEFAULT, _active);
                        } catch (Exception ex) {
                            Exceptions.printStackTrace(ex);
                        }
                    }
                };
            if (async) {
                RP.post(dothis);
            } else {
                dothis.run();
            }
        }
    }
    
    private synchronized Collection getConfigurations(boolean skipProfiles) {
        if (profiles == null && !skipProfiles) {
            profiles = createProfilesList();
        }
        skipProfiles |= !Boolean.parseBoolean(NbBundle.getMessage(M2ConfigProvider.class, "ProjectConfigurationProvider.ExportProfiles"));
        if (shared == null) {
            //read from auxconf
            shared = readConfigurations(aux, project.getProjectDirectory(), true);
        }
        if (nonshared == null) {
            //read from auxconf
            nonshared = readConfigurations(aux, project.getProjectDirectory(), false);
        }
        if (provided == null) {
            provided = createProvidedList();
        }
        Collection toRet = new TreeSet();
        toRet.add(DEFAULT);
        toRet.addAll(shared);
        toRet.addAll(nonshared);
        toRet.addAll(provided);
        if (!skipProfiles) {
            //prevent duplicates in the list
            Iterator it = profiles.iterator();
            while (it.hasNext()) {
                M2Configuration c = it.next();
                if (!toRet.contains(c)) {
                    toRet.add(c);
                } else {
                    it.remove();
                }
            }
        }
        return toRet;
        
    }
    
    public @Override synchronized Collection getConfigurations() {
        return getConfigurations(false);
    }

    public M2Configuration getDefaultConfig() {
        return DEFAULT;
    }
    
    public synchronized Collection getProfileConfigurations() {
        getConfigurations();
        return profiles;
    }
    
    public synchronized Collection getSharedConfigurations() {
        getConfigurations();
        return shared;
    }
    
    public synchronized Collection getNonSharedConfigurations() {
        getConfigurations();
        return nonshared;
    }
    
    public synchronized Collection getProvidedConfigurations() {
        getConfigurations();
        return provided;
    }
    
    public @Override boolean hasCustomizer() {
        return true;
    }

    public @Override void customize() {
        CustomizerProviderImpl prv = project.getLookup().lookup(CustomizerProviderImpl.class);
        prv.showCustomizer(ModelHandle2.PANEL_CONFIGURATION);
    }

    public @Override boolean configurationsAffectAction(String action) {
        return !ActionProvider.COMMAND_DELETE.equals(action) && !ActionProvider.COMMAND_COPY.equals(action) && !ActionProvider.COMMAND_MOVE.equals(action);
    }
    
    private M2Configuration internalActive() {
        M2Configuration c = this.activeOverride.get();
        return c != null ? c : active;
    }

    public @Override M2Configuration getActiveConfiguration() {
        M2Configuration _active;
        Collection confs;
        Boolean b = inConfigInit.get();
        _active  = activeOverride.get();
        if (_active != null) {
            // explicit temporary override, skip all the 'is still there' & fire change logic.
            return _active;
        }
        synchronized (this) {
            _active = active;
            confs = getConfigurations(false);
            String initAct = getInitialActive();
            OUTER: if (initAct != null) {
                for (M2Configuration conf : confs) {
                    if (initAct.equals(conf.getId())) {
                        if (_active != conf) {
                            _active = conf;
                            break OUTER;
                        } else {
                            _active = conf;
                        }
                        initAct = null;
                        break;
                    }
                }
                if (b == null && initAct != null) {
                    RP.post(new Runnable() {
                        public @Override void run() {
                            try {
                                doSetActiveConfiguration(DEFAULT, null);
                            } catch (Exception ex) {
                                Exceptions.printStackTrace(ex);
                            }
                        }
                    });
                }
                initialActive = null; //here we reset the initial active field value to prevent this block from happening exactly once.
            }
            if (b == null) {
                active = _active;
            }
        }
        checkActiveAgainstAll(confs, true);
        return _active;
    }
    
    public void setConfigurations(List shared, List nonshared, boolean includeProfiles) {
        writeAuxiliaryData(aux, true, shared);
        writeAuxiliaryData(aux, false, nonshared);
        synchronized (this) {
            this.shared = new TreeSet(shared);
            this.nonshared = new TreeSet(nonshared);
            //#174637
            if (active != null) {
                if (shared.contains(active)) {
                    M2Configuration newActive = shared.get(shared.indexOf(active));
                    //can have different content
                    active = newActive;
                }
                if (nonshared.contains(active)) {
                    M2Configuration newActive = nonshared.get(nonshared.indexOf(active));
                    //can have different content
                    active = newActive;
                }
            }
            this.profiles = null;
        }
        firePropertyChange();
    }

    public @Override void setActiveConfiguration(M2Configuration configuration) throws IllegalArgumentException, IOException {
        M2Configuration _active;
        synchronized (this) {
            if (active == configuration || (active != null && active.equals(configuration))) {
                return;
            }
            _active = active;
        }
        doSetActiveConfiguration(configuration, _active);
        NbMavenProject.fireMavenProjectReload(project);
    }

    private void doSetActiveConfiguration(M2Configuration newone, M2Configuration old) throws IllegalArgumentException, IOException {
        M2Configuration _active;
        synchronized (this) {
            active = newone;
            writeAuxiliaryData(
                    aux,
                    ACTIVATED, active.getId());
            _active = active;
        }
        assert !Thread.holdsLock(this);
        support.firePropertyChange(PROP_CONFIGURATION_ACTIVE, old, _active);
    }

    private SortedSet createProfilesList() {
        List profs = profileHandler.getAllProfiles();
        SortedSet config = new TreeSet();
//        config.add(DEFAULT);
        for (String prof : profs) {
            M2Configuration c = new M2Configuration(prof, project.getProjectDirectory());
            c.setActivatedProfiles(Collections.singletonList(prof));
            config.add(c);
        }
        return config;
    }
    
    private static void addProfileNames(Set names, Set profile) {
        for (M2Configuration c : profile) {
            names.add(c.getId());
        }
    }
    
    private Lookup.Result actionProviders;
    
    // @GuardedBy(this)
    private SortedSet createProvidedList() {
        SortedSet result = new TreeSet<>();
        provided = result;
        
        Boolean b = inConfigInit.get();
        inConfigInit.set(true);
        try {
            Collection providers;
            synchronized (this) {
                Lookup.Result r = actionProviders;
                if (r == null) {
                    r = project.getLookup().lookupResult(MavenActionsProvider.class);
                }
                providers = new ArrayList<>(r.allInstances());
                // initialize the listener after initial Lookup, otherwise the event may be fired. That could flush() 
                // e.g. in the middle of getConfigurations() processing.
                if (actionProviders == null) {
                    actionProviders = r;
                    actionProviders.addLookupListener(new LookupListener() {
                        @Override
                        public void resultChanged(LookupEvent ev) {
                            flush();
                        }
                    });
                }
            }
            for (MavenActionsProvider p : providers) {
                if (p == this) {
                    continue;
                }
                if (p instanceof AbstractMavenActionsProvider) {
                    List profiles;
                    M2Configuration c = setLocalConfiguration(DEFAULT);
                    try {
                        profiles = ((AbstractMavenActionsProvider) p).getRawMappings().getProfiles();
                        if (profiles.isEmpty()) {
                            continue;
                        }
                    } finally {
                        setLocalConfiguration(c);
                    }
                    Set knownIds = new HashSet<>();
                    addProfileNames(knownIds, this.profiles);
                    addProfileNames(knownIds, this.shared);
                    addProfileNames(knownIds, this.nonshared);

                    for (NetbeansActionProfile prof : profiles) {
                        if (!knownIds.contains(prof.getId())) {
                            M2Configuration cfg = new M2Configuration(prof.getId(), project.getProjectDirectory()) {
                                {
                                    if (p instanceof ActionNameProvider) {
                                        reader = DefaultActionGoalProvider.createI18nReader(((ActionNameProvider)p).getTranslations());
                                    }
                                }
                                @Override
                                public ActionToGoalMapping getRawMappings() {
                                    try (InputStream istm = getActionDefinitionStream()) {
                                        if (istm != null) {
                                            return super.getRawMappings();
                                        }
                                    } catch (IOException ex) {
                                        // silently ignore
                                    }
                                    ActionToGoalMapping m = new ActionToGoalMapping();
                                    ActionToGoalMapping allMappings;
                                    M2Configuration save = setLocalConfiguration(getDefaultConfig());
                                    try {
                                        allMappings = ((AbstractMavenActionsProvider) p).getRawMappings();
                                    } finally {
                                        setLocalConfiguration(save);
                                    }
                                    List profiles = allMappings.getProfiles();
                                    for (NetbeansActionProfile p : profiles) {
                                        if (prof.getId().equals(p.getId())) {
                                            for (NetbeansActionMapping am : p.getActions()) {
                                                m.addAction(am);
                                            }
                                            // default actions are handled in the UI, should not be added
                                            // as they are not customized by this profile (?)
                                        }
                                    }
                                    return m;
                                }
                            };
                            cfg.setDisplayName(prof.getDisplayName());
                            result.add(cfg);
                        }
                    }
                }
            }
            return result;
        } finally {
            inConfigInit.set(b);
        }
    }

    public @Override synchronized void addPropertyChangeListener(PropertyChangeListener lst) {
        if (support.getPropertyChangeListeners().length == 0) {
            project.getProjectWatcher().addPropertyChangeListener(propertyChange);
        }
        support.addPropertyChangeListener(lst);

    }

    public @Override synchronized void removePropertyChangeListener(PropertyChangeListener lst) {
        support.removePropertyChangeListener(lst);
        if (support.getPropertyChangeListeners().length == 0) {
            project.getProjectWatcher().removePropertyChangeListener(propertyChange);
        }
    }


    private void firePropertyChange() {
        support.firePropertyChange(ProjectConfigurationProvider.PROP_CONFIGURATIONS, null, null);
    }
    
    

    public static void writeAuxiliaryData(AuxiliaryConfiguration conf, String property, String value) {
        Element el = conf.getConfigurationFragment(ROOT, NAMESPACE, false);
        if (el == null) {
            el = XMLUtil.createDocument(ROOT, NAMESPACE, null, null).getDocumentElement();
        }
        Element enEl;
        NodeList list = el.getElementsByTagNameNS(NAMESPACE, property);
        if (list.getLength() > 0) {
            enEl = (Element)list.item(0);
        } else {
            enEl = el.getOwnerDocument().createElementNS(NAMESPACE, property);
            el.appendChild(enEl);
        }
        enEl.setTextContent(value);
        conf.putConfigurationFragment(el, false);
    }

    private static void writeAuxiliaryData(AuxiliaryConfiguration conf, boolean shared, List configs) {
        if (configs.isEmpty()) {//#226017
            conf.removeConfigurationFragment(ROOT, NAMESPACE, shared);
            return;
        }
        Element el = conf.getConfigurationFragment(ROOT, NAMESPACE, shared);
        if (el == null) {
            el = XMLUtil.createDocument(ROOT, NAMESPACE, null, null).getDocumentElement();
        }
        Element enEl;
        NodeList list = el.getElementsByTagNameNS(NAMESPACE, CONFIGURATIONS);
        if (list.getLength() > 0) {
            enEl = (Element)list.item(0);
            NodeList nl = enEl.getChildNodes();
            int len = nl.getLength();
            for (int i = 0; i < len; i++) {
                enEl.removeChild(nl.item(0));
            }
        } else {
            enEl = el.getOwnerDocument().createElementNS(NAMESPACE, CONFIGURATIONS);
            el.appendChild(enEl);
        }
        for (M2Configuration config : configs) {
            Element child  = enEl.getOwnerDocument().createElementNS(NAMESPACE, CONFIG);
            child.setAttribute(CONFIG_ID_ATTR, config.getId());
            child.setAttribute(CONFIG_PROFILES_ATTR, StringUtils.join(config.getActivatedProfiles().iterator(), " "));
            for (Map.Entry entry : config.getProperties().entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key != null && value != null) {
                    Element prop  = enEl.getOwnerDocument().createElementNS(NAMESPACE, PROPERTY);
                    prop.setAttribute(PROPERTY_NAME_ATTR, key);
                    prop.setTextContent(value);
                    child.appendChild(prop);
                }
            }
            enEl.appendChild(child);
        }
        conf.putConfigurationFragment(el, shared);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy