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

com.github.alexfalappa.nbspringboot.projects.service.impl.SpringBootServiceImpl Maven / Gradle / Ivy

/*
 * 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.impl;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.PrintWriter;
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 java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.springframework.boot.bind.RelaxedNames;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
import org.springframework.boot.configurationmetadata.SimpleConfigurationMetadataRepository;

import com.github.alexfalappa.nbspringboot.Utils;
import com.github.alexfalappa.nbspringboot.projects.service.api.HintProvider;
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.regex.Pattern.compile;

/**
 * Project wide {@link SpringBootService} implementation.
 * 

* It scans the classpath for {@code META-INF/spring-configuration-metadata.json} files, then unmarshals the files into the * corresponding {@code ConfigurationMetadata} classes and maintains indexed structures extracted out of it. *

* Registered for maven projects with jar and war packaging. * * @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 static final Pattern PATTERN_ARRAY_NOTATION = compile("(.+)\\[\\d+\\]"); private static final NoopHintProvider NOOP_HINT_PROVIDER = new NoopHintProvider(); private SimpleConfigurationMetadataRepository repo = new SimpleConfigurationMetadataRepository(); private final Map reposInJars = new HashMap<>(); private NbMavenProjectImpl mvnPrj; private ClassPath cpExec; private Map cachedProperties; private Map cachedDepsPresence = new HashMap<>(); private final Set collectionProperties = new HashSet<>(); private final Set mapProperties = new HashSet<>(); private final Map providerMap = new HashMap<>(); public SpringBootServiceImpl(Project p) { if (p instanceof NbMavenProjectImpl) { this.mvnPrj = (NbMavenProjectImpl) p; } final FileObject projectDirectory = mvnPrj.getProjectDirectory(); logger.log(Level.INFO, "Creating Spring Boot service for project {0}", FileUtil.getFileDisplayName(projectDirectory)); } @Override public void refresh() { logger.info("Refreshing Spring Boot service"); // check maven project has a dependency starting with 'spring-boot' logger.fine("Checking maven project has a spring boot dependency"); boolean springBootAvailable = dependencyArtifactIdContains(mvnPrj.getProjectWatcher(), "spring-boot"); // clear and exit if no spring boot dependency detected if (!springBootAvailable) { reposInJars.clear(); collectionProperties.clear(); mapProperties.clear(); // TODO delete nbactions.xml file from project dir ? return; } cachedDepsPresence.clear(); if (cpExec == null) { init(); } else { // build configuration metadata repository updateConfigRepo(); } // adjust the nbactions.xml file depending on boot version adjustNbActions(); } @Override public ClassPath getManagedClassPath() { return cpExec; } @Override public Set getPropertyNames() { if (cpExec == null) { init(); } return cachedProperties.keySet(); } @Override public Set getCollectionPropertyNames() { return collectionProperties; } @Override public Set getMapPropertyNames() { return mapProperties; } @Override public HintProvider getHintProvider(String name) { return providerMap.getOrDefault(name, NOOP_HINT_PROVIDER); } @Override public ConfigurationMetadataProperty getPropertyMetadata(String propertyName) { if (cpExec == null) { init(); } if (cachedProperties != null) { // generate and try relaxed variants for (String relaxedName : new RelaxedNames(propertyName)) { if (cachedProperties.containsKey(relaxedName)) { return cachedProperties.get(relaxedName); } else { // try to interpret array notation (strip '[index]' from pName) Matcher mArrNot = PATTERN_ARRAY_NOTATION.matcher(relaxedName); if (mArrNot.matches()) { return cachedProperties.get(mArrNot.group(1)); } else { // try to interpret map notation (see if pName starts with a set of known map props) for (String mapPropertyName : getMapPropertyNames()) { if (relaxedName.startsWith(mapPropertyName)) { return cachedProperties.get(mapPropertyName); } } } } } } return null; } @Override public List queryPropertyMetadata(String filter) { if (cpExec == null) { init(); } List ret = new LinkedList<>(); if (cachedProperties != null) { for (String propName : getPropertyNames()) { if (filter == null || propName.contains(filter)) { ret.add(cachedProperties.get(propName)); } } } return ret; } @Override public boolean hasPomDependency(String artifactId) { if (cpExec == null) { init(); } if (cachedDepsPresence != null) { if (!cachedDepsPresence.containsKey(artifactId)) { cachedDepsPresence.put(artifactId, dependencyArtifactIdContains(mvnPrj.getProjectWatcher(), artifactId)); } return cachedDepsPresence.get(artifactId); } return false; } @Override public String getRestartEnvVarName() { return isBoot2() ? ENV_RESTART_20 : ENV_RESTART_15; } @Override public String getPluginPropsPrefix() { return isBoot2() ? "spring-boot.run" : "run"; } private void init() { if (mvnPrj == null) { return; } logger.info("Initializing Spring Boot service"); // check maven project has a dependency starting with 'spring-boot' boolean springBootAvailable = dependencyArtifactIdContains(mvnPrj.getProjectWatcher(), "spring-boot"); logger.fine("Checking maven project has a spring boot dependency"); // early exit if no spring boot dependency detected if (!springBootAvailable) { return; } logger.log(INFO, "Initializing SpringBootService for project {0}", new Object[]{mvnPrj.toString()}); cachedDepsPresence.clear(); // set up a reference to the execute classpath object cpExec = Utils.execClasspathForProj(mvnPrj); if (cpExec != null) { // 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(); } } }); // populate hint providers map ClasspathInfo cpInfo = ClasspathInfo.create(mvnPrj.getProjectDirectory()); providerMap.put("logger-name", new LoggerNameHintProvider(cpInfo.getClassIndex())); providerMap.put("class-reference", new ClassReferenceHintProvider(cpInfo.getClassIndex(), cpExec)); providerMap.put("handle-as", new HandleAsHintProvider(mvnPrj)); // build configuration properties maps updateConfigRepo(); } } // Update internal configuration metadata repository private void updateConfigRepo() { logger.fine("Updating config metadata repo"); repo = new SimpleConfigurationMetadataRepository(); final List 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 entry : cachedProperties.entrySet()) { final String type = entry.getValue().getType(); if (type != null) { final String key = entry.getKey(); if (type.startsWith("java.util.Map<")) { mapProperties.add(key); } if (type.startsWith("java.util.List<") || type.startsWith("java.util.Set<") || type.startsWith("java.util.Collection<")) { collectionProperties.add(key); } } } } // check if any of the project dependencies artifact ids contains the given string 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; } // tell if the project currently uses Spring Boot 2.x private boolean isBoot2() { boolean flag = false; if (mvnPrj != null) { // retrieve boot version from parent pom declaration if present // TODO also look into parent hierarchy // TODO also try to look in dependency management section (inclusion of spring boot BOM) String bootVer = mvnPrj.getOriginalMavenProject().getParentArtifact().getVersion(); flag = bootVer.startsWith("2"); } return flag; } private void adjustNbActions() { final FileObject foPrjDir = mvnPrj.getProjectDirectory(); FileObject foNbAct = foPrjDir.getFileObject("nbactions.xml"); if (foNbAct != null) { logger.fine("Adjusting nbactions.xml file"); try (FileLock lock = foNbAct.lock()) { try (PrintWriter pw = new PrintWriter(foPrjDir.createAndOpen("nbactions.tmp"))) { if (isBoot2()) { for (String line : foNbAct.asLines()) { line = line.replace(ENV_RESTART_15, ENV_RESTART_20); line = line.replace("





© 2015 - 2025 Weber Informatics LLC | Privacy Policy