com.github.alexfalappa.nbspringboot.projects.service.spi.SpringBootServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.github.alexfalappa.nbspringboot.projects.service.spi.SpringBootServiceImpl.java

Source

/*
 * Copyright 2016 Alessandro Falappa.
 *
 * Licensed 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 com.github.alexfalappa.nbspringboot.projects.service.spi;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
import org.springframework.boot.configurationmetadata.Hints;
import org.springframework.boot.configurationmetadata.SimpleConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.ValueHint;

import com.github.alexfalappa.nbspringboot.projects.service.api.SpringBootService;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;

/**
 * Project wide {@link SpringBootService} implementation.
 * <p>
 * It reads Spring Boot configuration properties metadata and maintaining indexed structures extracted out of it.
 * <p>
 * Registered for maven projects.
 *
 * @author Alessandro Falappa
 */
@ProjectServiceProvider(service = SpringBootService.class, projectType = {
        "org-netbeans-modules-maven/" + NbMavenProject.TYPE_JAR,
        "org-netbeans-modules-maven/" + NbMavenProject.TYPE_WAR })
public class SpringBootServiceImpl implements SpringBootService {

    private static final Logger logger = Logger.getLogger(SpringBootServiceImpl.class.getName());
    private static final String METADATA_JSON = "META-INF/spring-configuration-metadata.json";
    private SimpleConfigurationMetadataRepository repo = new SimpleConfigurationMetadataRepository();
    private final Map<String, ConfigurationMetadataRepository> reposInJars = new HashMap<>();
    private boolean springBootAvailable = false;
    private NbMavenProjectImpl mvnPrj;
    private ClassPath cpExec;
    private Map<String, ConfigurationMetadataProperty> cachedProperties;
    private final Set<String> collectionProperties = new HashSet<>();
    private final Set<String> mapProperties = new HashSet<>();

    public SpringBootServiceImpl(Project p) {
        if (p instanceof NbMavenProjectImpl) {
            this.mvnPrj = (NbMavenProjectImpl) p;
        }
        logger.log(Level.INFO, "Creating Spring Boot service for project {0}",
                FileUtil.getFileDisplayName(p.getProjectDirectory()));
    }

    private void init() {
        if (mvnPrj == null) {
            return;
        }
        logger.info("Initializing SpringBoot service");
        // check maven project has a dependency starting with 'spring-boot'
        logger.fine("Checking maven project has a spring boot dependency");
        springBootAvailable = dependencyArtifactIdContains(mvnPrj.getProjectWatcher(), "spring-boot");
        // early exit if no spring boot dependency detected
        if (!springBootAvailable) {
            return;
        }
        logger.log(INFO, "Initializing SpringBootService for project {0}", new Object[] { mvnPrj.toString() });
        // set up a reference to the execute classpath object
        Sources srcs = ProjectUtils.getSources(mvnPrj);
        SourceGroup[] srcGroups = srcs.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
        boolean srcGroupFound = false;
        for (SourceGroup group : srcGroups) {
            if (group.getName().toLowerCase().contains("source")) {
                srcGroupFound = true;
                cpExec = ClassPath.getClassPath(group.getRootFolder(), ClassPath.EXECUTE);
                break;
            }
        }
        if (!srcGroupFound) {
            logger.log(WARNING, "No sources found for project: {0}", new Object[] { mvnPrj.toString() });
        }
        if (cpExec != null) {
            // check if completion of configuration properties is possible
            try {
                logger.fine(
                        "Checking spring boot ConfigurationProperties class is on the project execution classpath");
                cpExec.getClassLoader(false)
                        .loadClass("org.springframework.boot.context.properties.ConfigurationProperties");
            } catch (ClassNotFoundException ex) {
                // no completion
            }
            // listen for pom changes
            logger.info("Adding maven pom listener...");
            mvnPrj.getProjectWatcher().addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    final String propertyName = String.valueOf(evt.getPropertyName());
                    logger.log(FINE, "Maven pom change ({0})", propertyName);
                    if (propertyName.equals("MavenProject")) {
                        refresh();
                    }
                }
            });
            // build configuration properties maps
            updateConfigRepo();
        }
    }

    @Override
    public void refresh() {
        logger.info("Refreshing SpringBoot service");
        // check maven project has a dependency starting with 'spring-boot'
        logger.fine("Checking maven project has a spring boot dependency");
        springBootAvailable = dependencyArtifactIdContains(mvnPrj.getProjectWatcher(), "spring-boot");
        // clear and exit if no spring boot dependency detected
        if (!springBootAvailable) {
            reposInJars.clear();
            collectionProperties.clear();
            mapProperties.clear();
            return;
        }
        if (cpExec == null) {
            init();
        } else {
            // check if completion of configuration properties is possible
            try {
                logger.fine(
                        "Checking spring boot ConfigurationProperties class is on the project execution classpath");
                cpExec.getClassLoader(false)
                        .loadClass("org.springframework.boot.context.properties.ConfigurationProperties");
            } catch (ClassNotFoundException ex) {
                // no completion
            }
            // build configuration metadata repository
            updateConfigRepo();
        }
    }

    @Override
    public ClassPath getManagedClassPath() {
        return cpExec;
    }

    @Override
    public Set<String> getPropertyNames() {
        if (cpExec == null) {
            init();
        }
        return cachedProperties.keySet();
    }

    @Override
    public ConfigurationMetadataProperty getPropertyMetadata(String propertyName) {
        if (cpExec == null) {
            init();
        }
        return cachedProperties.get(propertyName);
    }

    @Override
    public List<ConfigurationMetadataProperty> queryPropertyMetadata(String filter) {
        if (cpExec == null) {
            init();
        }
        List<ConfigurationMetadataProperty> ret = new LinkedList<>();
        for (String propName : getPropertyNames()) {
            if (filter == null || propName.contains(filter)) {
                ret.add(cachedProperties.get(propName));
            }
        }
        return ret;
    }

    @Override
    public List<ValueHint> queryHintMetadata(String propertyName, String filter) {
        if (cpExec == null) {
            init();
        }
        List<ValueHint> ret = new LinkedList<>();
        if (getPropertyNames().contains(propertyName)) {
            Hints hints = cachedProperties.get(propertyName).getHints();
            for (ValueHint valueHint : hints.getValueHints()) {
                if (filter == null || valueHint.getValue().toString().contains(filter)) {
                    ret.add(valueHint);
                }
            }
        }
        return ret;
    }

    // Update internal configuration metadata repository
    private void updateConfigRepo() {
        logger.fine("Updating config metadata repo");
        repo = new SimpleConfigurationMetadataRepository();
        final List<FileObject> cfgMetaFiles = cpExec.findAllResources(METADATA_JSON);
        for (FileObject fo : cfgMetaFiles) {
            try {
                ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder
                        .create();
                ConfigurationMetadataRepository currRepo;
                FileObject archiveFo = FileUtil.getArchiveFile(fo);
                if (archiveFo != null) {
                    // parse and cache configuration metadata from JSON file in jar
                    String archivePath = archiveFo.getPath();
                    if (!reposInJars.containsKey(archivePath)) {
                        logger.log(INFO, "Unmarshalling configuration metadata from {0}",
                                FileUtil.getFileDisplayName(fo));
                        ConfigurationMetadataRepository jarRepo = builder.withJsonResource(fo.getInputStream())
                                .build();
                        reposInJars.put(archivePath, jarRepo);
                    }
                    currRepo = reposInJars.get(archivePath);
                } else {
                    // parse configuration metadata from standalone JSON file (usually produced by spring configuration processor)
                    logger.log(INFO, "Unmarshalling configuration metadata from {0}",
                            FileUtil.getFileDisplayName(fo));
                    currRepo = builder.withJsonResource(fo.getInputStream()).build();
                }
                repo.include(currRepo);
            } catch (Exception ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        // update cached values
        cachedProperties = repo.getAllProperties();
        // extract collection/map properties names based on heuristics
        for (Map.Entry<String, ConfigurationMetadataProperty> entry : cachedProperties.entrySet()) {
            final String type = entry.getValue().getType();
            if (type != null) {
                final String key = entry.getKey();
                if (type.contains("Map<")) {
                    mapProperties.add(key);
                }
                if (type.contains("List<") || type.contains("Set<") || type.contains("Collection<")) {
                    collectionProperties.add(key);
                }
            }
        }
        System.out.printf("Collections: %s%n", collectionProperties);
        System.out.printf("       Maps: %s%n", mapProperties);
    }

    private boolean dependencyArtifactIdContains(NbMavenProject nbMvn, String artifactId) {
        MavenProject mPrj = nbMvn.getMavenProject();
        for (Object o : mPrj.getDependencies()) {
            Dependency d = (Dependency) o;
            if (d.getArtifactId().contains(artifactId)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Set<String> getCollectionPropertyNames() {
        return collectionProperties;
    }

    @Override
    public Set<String> getMapPropertyNames() {
        return mapProperties;
    }
}