diff --git a/eclipse-distribution/org.springframework.boot.ide.repository.e47/category.xml b/eclipse-distribution/org.springframework.boot.ide.repository.e47/category.xml index d0bafb3c7..f69433447 100644 --- a/eclipse-distribution/org.springframework.boot.ide.repository.e47/category.xml +++ b/eclipse-distribution/org.springframework.boot.ide.repository.e47/category.xml @@ -79,6 +79,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse-distribution/pom.xml b/eclipse-distribution/pom.xml index 6530e491b..bba04c789 100644 --- a/eclipse-distribution/pom.xml +++ b/eclipse-distribution/pom.xml @@ -113,16 +113,16 @@ p2 http://download.eclipse.org/releases/oxygen/ - + + latest-m2e p2 diff --git a/eclipse-language-servers/org.springframework.tooling.boot.java.ls.feature/feature.xml b/eclipse-language-servers/org.springframework.tooling.boot.java.ls.feature/feature.xml index f928a6a9b..e344c175a 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.java.ls.feature/feature.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.java.ls.feature/feature.xml @@ -8,6 +8,7 @@ + - - - diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/META-INF/MANIFEST.MF index 21a586abd..04f7dc0dd 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/META-INF/MANIFEST.MF @@ -12,11 +12,11 @@ Require-Bundle: org.eclipse.ui, org.eclipse.core.filebuffers, org.eclipse.ui.navigator, org.eclipse.equinox.registry, - org.springsource.ide.eclipse.commons.livexp, + org.springsource.ide.eclipse.commons.livexp;bundle-version="3.9.1", com.google.guava, org.eclipse.core.jobs, org.eclipse.core.runtime;bundle-version="3.13.0", - org.springsource.ide.eclipse.commons.core + org.springsource.ide.eclipse.commons.core;bundle-version="3.9.1" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-Activator: org.springframework.tooling.ls.eclipse.gotosymbol.GotoSymbolPlugin Bundle-ActivationPolicy: lazy diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/src/org/springframework/tooling/ls/eclipse/gotosymbol/dialogs/GotoSymbolDialog.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/src/org/springframework/tooling/ls/eclipse/gotosymbol/dialogs/GotoSymbolDialog.java index 0ed154737..a6f0aba60 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/src/org/springframework/tooling/ls/eclipse/gotosymbol/dialogs/GotoSymbolDialog.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.gotosymbol/src/org/springframework/tooling/ls/eclipse/gotosymbol/dialogs/GotoSymbolDialog.java @@ -14,9 +14,13 @@ package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs; import java.util.ArrayList; import java.util.List; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; @@ -72,6 +76,21 @@ public class GotoSymbolDialog extends PopupDialog { return null; } } + + private static class GotoSymbolsLabelProvider extends SymbolsLabelProvider { + @Override + protected int getMaxSeverity(IResource resource, SymbolInformation symbolInformation) + throws CoreException, BadLocationException { + int maxSeverity = -1; + for (IMarker marker : resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_ZERO)) { + int offset = marker.getAttribute(IMarker.CHAR_START, -1); + if (offset != -1) { + maxSeverity = Math.max(maxSeverity, marker.getAttribute(IMarker.SEVERITY, -1)); + } + } + return maxSeverity; + } + } private GotoSymbolDialogModel model; private List disposables = new ArrayList<>(); @@ -222,7 +241,7 @@ public class GotoSymbolDialog extends PopupDialog { TreeViewer viewer = new TreeViewer(dialogArea, SWT.SINGLE); GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl()); viewer.setContentProvider(new SymbolsContentProvider()); - viewer.setLabelProvider(new SymbolsLabelProvider()); + viewer.setLabelProvider(new GotoSymbolsLabelProvider()); viewer.setUseHashlookup(true); disposables.add(model.getSymbols().onChange(UIValueListener.from((e, v) -> { viewer.refresh(); diff --git a/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml b/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml index d904c61d5..efd9b37a9 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml +++ b/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml @@ -32,6 +32,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java index 19885dbf5..145feba88 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java @@ -20,4 +20,31 @@ public class Annotations { public static final String PROFILE = "org.springframework.context.annotation.Profile"; public static final String COMPONENT = "org.springframework.stereotype.Component"; public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired"; + + public static final String SPRING_REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping"; + public static final String SPRING_GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping"; + public static final String SPRING_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping"; + public static final String SPRING_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping"; + public static final String SPRING_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping"; + public static final String SPRING_PATCH_MAPPING = "org.springframework.web.bind.annotation.PatchMapping"; + + public static final String CONDITIONAL = "org.springframework.context.annotation.Conditional"; + public static final String CONDITIONAL_ON_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"; + public static final String CONDITIONAL_ON_MISSING_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"; + public static final String CONDITIONAL_ON_PROPERTY = "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty"; + public static final String CONDITIONAL_ON_RESOURCE = "org.springframework.boot.autoconfigure.condition.ConditionalOnResource"; + public static final String CONDITIONAL_ON_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"; + public static final String CONDITIONAL_ON_MISSING_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass"; + public static final String CONDITIONAL_ON_CLOUD_PLATFORM = "org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform"; + public static final String CONDITIONAL_ON_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"; + public static final String CONDITIONAL_ON_NOT_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication"; + public static final String CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledInfoContributor"; + public static final String CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN = "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain"; + public static final String CONDITIONAL_ON_ENABLED_ENDPOINT = "org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint"; + public static final String CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator"; + public static final String CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression"; + public static final String CONDITIONAL_ON_JAVA = "org.springframework.boot.autoconfigure.condition.ConditionalOnJava"; + public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi"; + public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"; + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index 41be1c788..ea021fa6c 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -245,17 +245,17 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_REQUEST_MAPPING, + providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_GET_MAPPING, + providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_POST_MAPPING, + providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_PUT_MAPPING, + providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_DELETE_MAPPING, + providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_PATCH_MAPPING, + providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingHoverProvider()); providers.put(Annotations.PROFILE, new ActiveProfilesProvider()); @@ -263,45 +263,41 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { providers.put(Annotations.COMPONENT, new ComponentInjectionsHoverProvider()); providers.put(Annotations.BEAN, new BeanInjectedIntoHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL, + providers.put(Annotations.CONDITIONAL, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_BEAN, + providers.put(Annotations.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_MISSING_BEAN, + providers.put(Annotations.CONDITIONAL_ON_MISSING_BEAN, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_PROPERTY, + providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_RESOURCE, + providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_CLASS, + providers.put(Annotations.CONDITIONAL_ON_CLASS, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_MISSING_CLASS, + providers.put(Annotations.CONDITIONAL_ON_MISSING_CLASS, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_CLOUD_PLATFORM, + providers.put(Annotations.CONDITIONAL_ON_CLOUD_PLATFORM, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_WEB_APPLICATION, + providers.put(Annotations.CONDITIONAL_ON_WEB_APPLICATION, new ConditionalsLiveHoverProvider()); - providers.put( - org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_NOT_WEB_APPLICATION, + providers.put(Annotations.CONDITIONAL_ON_NOT_WEB_APPLICATION, new ConditionalsLiveHoverProvider()); - providers.put( - org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, + providers.put(Annotations.CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR, new ConditionalsLiveHoverProvider()); - providers.put( - org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, + providers.put(Annotations.CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_ENABLED_ENDPOINT, + providers.put(Annotations.CONDITIONAL_ON_ENABLED_ENDPOINT, new ConditionalsLiveHoverProvider()); - providers.put( - org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, + providers.put(Annotations.CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_EXPRESSION, + providers.put(Annotations.CONDITIONAL_ON_EXPRESSION, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_JAVA, + providers.put(Annotations.CONDITIONAL_ON_JAVA, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_JNDI, + providers.put(Annotations.CONDITIONAL_ON_JNDI, new ConditionalsLiveHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_SINGLE_CANDIDATE, + providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, new ConditionalsLiveHoverProvider()); return new BootJavaHoverProvider(this, javaProjectFinder, providers, runningAppProvider); @@ -309,17 +305,17 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, JavaProjectFinder projectFinder) { HashMap providers = new HashMap<>(); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_REQUEST_MAPPING, + providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_GET_MAPPING, + providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingSymbolProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_POST_MAPPING, + providers.put(Annotations.SPRING_POST_MAPPING, new RequestMappingSymbolProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_PUT_MAPPING, + providers.put(Annotations.SPRING_PUT_MAPPING, new RequestMappingSymbolProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_DELETE_MAPPING, + providers.put(Annotations.SPRING_DELETE_MAPPING, new RequestMappingSymbolProvider()); - providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_PATCH_MAPPING, + providers.put(Annotations.SPRING_PATCH_MAPPING, new RequestMappingSymbolProvider()); providers.put(Annotations.BEAN, new BeansSymbolProvider()); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java index 19adceff4..6edc357b9 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java @@ -12,7 +12,6 @@ package org.springframework.ide.vscode.boot.java.conditionals; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -27,13 +26,11 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.json.JSONArray; -import org.json.JSONObject; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.utils.HoverContentUtils; +import org.springframework.ide.vscode.commons.boot.app.cli.LiveConditional; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -55,28 +52,45 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { @Override public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { - if (runningApps.length > 0) { - ConditionalParserFromRunningApp parser = new ConditionalParserFromRunningApp(); - Optional> val = parser.parse(annotation, runningApps); - if (val.isPresent()) { - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return ImmutableList.of(hoverRange); - } + Optional> val = getMatchedLiveConditionals(annotation, runningApps); + if (val.isPresent()) { + Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + return ImmutableList.of(hoverRange); } - } catch (BadLocationException e) { + } catch (Exception e) { Log.log(e); } return null; } + private Optional> getMatchedLiveConditionals(Annotation annotation, + SpringBootApp[] runningApps) throws Exception { + if (runningApps != null) { + List all = new ArrayList<>(); + + for (SpringBootApp springBootApp : runningApps) { + springBootApp.getLiveConditionals().ifPresent((conditionals) -> { + conditionals.stream().forEach((conditional) -> { + if (matchesAnnotation(annotation, conditional)) { + all.add(conditional); + } + }); + }); + } + if (!all.isEmpty()) { + return Optional.of(all); + } + } + return Optional.empty(); + } + private CompletableFuture provideHover(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { List> hoverContent = new ArrayList<>(); - ConditionalParserFromRunningApp parser = new ConditionalParserFromRunningApp(); - Optional> val = parser.parse(annotation, runningApps); + Optional> val = getMatchedLiveConditionals(annotation, runningApps); if (val.isPresent()) { addHoverContent(val.get(), hoverContent); @@ -96,151 +110,43 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { return null; } - private void addHoverContent(List conditions, - List> hoverContent) throws Exception { - for (int i = 0; i < conditions.size(); i++) { - RunningAppConditional condition = conditions.get(i); - hoverContent.add(Either.forLeft(condition.message)); - hoverContent.add(Either - .forLeft(HoverContentUtils.getProcessInformation(condition.app))); + private void addHoverContent(List conditionals, List> hoverContent) + throws Exception { + for (int i = 0; i < conditionals.size(); i++) { + LiveConditional conditional = conditionals.get(i); + hoverContent.add(Either.forLeft(conditional.getMessage())); + hoverContent.add(Either.forLeft(HoverContentUtils.getProcessInformation(conditional.getProcessId(), conditional.getProcessName()))); - if (i < conditions.size() - 1) { + if (i < conditionals.size() - 1) { hoverContent.add(Either.forLeft("---")); } } } /** - * Parsers @ConditionalOn annotations from a spring boot running app's - * autoconfig report. An example of a conditional that is parsed from a running - * app's autoconfig report would be: - * - * {"TraceRepositoryAutoConfiguration#traceRepository":[{"condition":"OnBeanCondition","message":"@ConditionalOnMissingBean - * (types: org.springframework.boot.actuate.trace.TraceRepository; - * SearchStrategy: all) did not find any beans"}] * + * @param annotation + * @param jsonKey + * @return true if the annotation matches the information in the json key from + * the running app. */ - public static class ConditionalParserFromRunningApp { + protected boolean matchesAnnotation(Annotation annotation, LiveConditional liveConditional) { - public Optional> parse(Annotation annotation, SpringBootApp[] runningApps) { + ASTNode parent = annotation.getParent(); + String rawJsonKey = liveConditional.getPositiveMatchKey(); - try { - List allConditionals = new ArrayList<>(); - for (SpringBootApp app : runningApps) { - String autoConfigRecord = app.getAutoConfigReport(); - if (autoConfigRecord != null) { - JSONObject autoConfigJson = new JSONObject(autoConfigRecord); - List conditionalsFromPositiveMatches = getConditionals(app, annotation, - autoConfigJson); - if (!conditionalsFromPositiveMatches.isEmpty()) { - allConditionals.addAll(conditionalsFromPositiveMatches); - } - } - } - if (!allConditionals.isEmpty()) { - return Optional.of(allConditionals); - } - } catch (Exception e) { - Log.log(e); - } - return Optional.empty(); - } - - /** - * - * @param app - * @param annotation - * @param autoConfigJson - * @return non-null list of conditionals parsed from an autoconfig report. List - * may be empty. - */ - private List getConditionals(SpringBootApp app, Annotation annotation, - JSONObject autoConfigJson) { - List conditions = new ArrayList<>(); - - getPositiveMatches(autoConfigJson).ifPresent((positiveMatches) -> { - Iterator pMKeys = positiveMatches.keys(); - while (pMKeys.hasNext()) { - String positiveMatchKey = pMKeys.next(); - if (matchesAnnotation(annotation, positiveMatchKey)) { - JSONArray matchList = (JSONArray) positiveMatches.get(positiveMatchKey); - matchList.forEach((match) -> { - if (match instanceof JSONObject) { - getMatchedCondition(app, (JSONObject) match, annotation) - .ifPresent((condition) -> conditions.add(condition)); - } - }); - } - } - }); - - return conditions; - } - - protected Optional getPositiveMatches(JSONObject autoConfigJson) { - - Iterator keys = autoConfigJson.keys(); - - while (keys.hasNext()) { - String key = keys.next(); - if ("positiveMatches".equals(key)) { - Object obj = autoConfigJson.get(key); - if (obj instanceof JSONObject) { - return Optional.of((JSONObject) obj); - } - } - } - return Optional.empty(); - } - - /** - * - * @param annotation - * @param jsonKey - * @return true if the annotation matches the information in the json key from - * the running app. - */ - protected boolean matchesAnnotation(Annotation annotation, String jsonKey) { - - ASTNode parent = annotation.getParent(); - if (parent instanceof MethodDeclaration) { - MethodDeclaration methodDec = (MethodDeclaration) parent; - IMethodBinding binding = methodDec.resolveBinding(); - String annotationDeclaringClassName = binding.getDeclaringClass().getName(); - String annotationMethodName = binding.getName(); - return jsonKey.contains(annotationDeclaringClassName) && jsonKey.contains(annotationMethodName); - } else if (parent instanceof TypeDeclaration) { - TypeDeclaration typeDec = (TypeDeclaration) parent; - String annotationDeclaringClassName = typeDec.resolveBinding().getName(); - return jsonKey.contains(annotationDeclaringClassName); - } - return false; - } - - protected Optional getMatchedCondition(SpringBootApp app, JSONObject conditionJson, - Annotation annotation) { - if (conditionJson != null) { - String condition = (String) conditionJson.get("condition"); - String message = (String) conditionJson.get("message"); - String annotationName = annotation.resolveTypeBinding().getName(); - if (message.contains(annotationName)) { - return Optional.of(new RunningAppConditional(app, condition, message)); - } - } - return Optional.empty(); + if (parent instanceof MethodDeclaration) { + MethodDeclaration methodDec = (MethodDeclaration) parent; + IMethodBinding binding = methodDec.resolveBinding(); + String annotationDeclaringClassName = binding.getDeclaringClass().getName(); + String annotationMethodName = binding.getName(); + return rawJsonKey.contains(annotationDeclaringClassName) && rawJsonKey.contains(annotationMethodName); + } else if (parent instanceof TypeDeclaration) { + TypeDeclaration typeDec = (TypeDeclaration) parent; + String annotationDeclaringClassName = typeDec.resolveBinding().getName(); + return rawJsonKey.contains(annotationDeclaringClassName); } + return false; } - public static class RunningAppConditional { - - public final String condition; - public final String message; - public final SpringBootApp app; - - public RunningAppConditional(SpringBootApp app, String condition, String message) { - this.condition = condition; - this.message = message; - this.app = app; - } - } } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/Constants.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/Constants.java deleted file mode 100644 index 37e1cd7a0..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/Constants.java +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.conditionals; - -public class Constants { - - public static final String CONDITIONAL = "org.springframework.context.annotation.Conditional"; - public static final String CONDITIONAL_ON_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"; - public static final String CONDITIONAL_ON_MISSING_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"; - public static final String CONDITIONAL_ON_PROPERTY = "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty"; - public static final String CONDITIONAL_ON_RESOURCE = "org.springframework.boot.autoconfigure.condition.ConditionalOnResource"; - public static final String CONDITIONAL_ON_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"; - public static final String CONDITIONAL_ON_MISSING_CLASS = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass"; - public static final String CONDITIONAL_ON_CLOUD_PLATFORM = "org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform"; - public static final String CONDITIONAL_ON_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"; - public static final String CONDITIONAL_ON_NOT_WEB_APPLICATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication"; - public static final String CONDITIONAL_ON_ENABLED_INFO_CONTRIBUTOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledInfoContributor"; - public static final String CONDITIONAL_ON_ENABLED_RESOURCE_CHAIN = "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain"; - public static final String CONDITIONAL_ON_ENABLED_ENDPOINT = "org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint"; - public static final String CONDITIONAL_ON_ENABLED_HEALTH_INDICATOR = "org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator"; - public static final String CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression"; - public static final String CONDITIONAL_ON_JAVA = "org.springframework.boot.autoconfigure.condition.ConditionalOnJava"; - public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi"; - public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"; -} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/Constants.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/Constants.java deleted file mode 100644 index 8d655ce30..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/Constants.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.requestmapping; - -/** - * @author Martin Lippert - */ -public class Constants { - - public static final String SPRING_REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping"; - public static final String SPRING_GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping"; - public static final String SPRING_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping"; - public static final String SPRING_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping"; - public static final String SPRING_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping"; - public static final String SPRING_PATCH_MAPPING = "org.springframework.web.bind.annotation.PatchMapping"; - -} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java index 620449c76..400826044 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java @@ -30,6 +30,7 @@ import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -142,7 +143,7 @@ public class RequestMappingSymbolProvider implements SymbolProvider { Annotation annotation = (Annotation) modifier; ITypeBinding resolvedType = annotation.resolveTypeBinding(); String annotationType = resolvedType.getQualifiedName(); - if (annotationType != null && Constants.SPRING_REQUEST_MAPPING.equals(annotationType)) { + if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) { return annotation; } } @@ -155,15 +156,15 @@ public class RequestMappingSymbolProvider implements SymbolProvider { ITypeBinding type = annotation.resolveTypeBinding(); if (type != null) { switch (type.getQualifiedName()) { - case Constants.SPRING_GET_MAPPING: + case Annotations.SPRING_GET_MAPPING: return new String[] { "GET" }; - case Constants.SPRING_POST_MAPPING: + case Annotations.SPRING_POST_MAPPING: return new String[] { "POST" }; - case Constants.SPRING_DELETE_MAPPING: + case Annotations.SPRING_DELETE_MAPPING: return new String[] { "DELETE" }; - case Constants.SPRING_PUT_MAPPING: + case Annotations.SPRING_PUT_MAPPING: return new String[] { "PUT" }; - case Constants.SPRING_PATCH_MAPPING: + case Annotations.SPRING_PATCH_MAPPING: return new String[] { "PATCH" }; } } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/HoverContentUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/HoverContentUtils.java index 5b34dea3c..7998a8443 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/HoverContentUtils.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/HoverContentUtils.java @@ -15,7 +15,11 @@ import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; public class HoverContentUtils { public static String getProcessInformation(SpringBootApp app) { - return "Process " + app.getProcessID() + ": " + app.getProcessName(); + return getProcessInformation(app.getProcessID(), app.getProcessName()); + } + + public static String getProcessInformation(String processId, String processName) { + return "Process " + processId + ": " + processName; } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java index ce876ca07..354c5cc64 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java @@ -67,7 +67,7 @@ public class ConditionalsLiveHoverTest { // Build a mock running boot app mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"ConditionalOnBeanConfig#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}]}}") .build(); @@ -91,7 +91,7 @@ public class ConditionalsLiveHoverTest { // Build a mock running boot app mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") .build(); @@ -114,7 +114,7 @@ public class ConditionalsLiveHoverTest { // Build a mock running boot app mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}") .build(); @@ -156,19 +156,19 @@ public class ConditionalsLiveHoverTest { // Build a mock running boot app mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") .build(); mockAppProvider.builder().isSpringBootApp(true).port("1001").processId("80000").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") .build(); mockAppProvider.builder().isSpringBootApp(true).port("1002").processId("90000").host("cfapps.io") .processName("test-conditionals-live-hover") - .getAutoConfigReport( + .getLiveConditionals( "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") .build(); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java index d5136414e..07a5664c9 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java @@ -27,9 +27,9 @@ import org.apache.commons.io.FileUtils; import org.eclipse.lsp4j.SymbolInformation; import org.junit.Before; import org.junit.Test; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; -import org.springframework.ide.vscode.boot.java.requestmapping.Constants; import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; import org.springframework.ide.vscode.commons.maven.MavenCore; @@ -53,7 +53,7 @@ public class SpringIndexerTest { @Before public void setup() throws Exception { symbolProviders = new HashMap<>(); - symbolProviders.put(Constants.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); + symbolProviders.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); harness = BootLanguageServerHarness.builder().build(); } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java index b06af7246..409fd34cb 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java @@ -19,8 +19,8 @@ import java.util.Collection; import org.mockito.Mockito; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; -import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; +import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.util.ExceptionUtil; import com.google.common.collect.ImmutableList; @@ -57,6 +57,8 @@ public class MockRunningAppProvider { public static class MockAppBuilder { public final SpringBootApp app = mock(SpringBootApp.class); private MockRunningAppProvider runningAppProvider; + private String processId; + private String processName; public MockAppBuilder(MockRunningAppProvider runningAppProvider) { this.runningAppProvider = runningAppProvider; @@ -77,11 +79,13 @@ public class MockRunningAppProvider { } public MockAppBuilder processId(String processId) { + this.processId = processId; when(app.getProcessID()).thenReturn(processId); return this; } public MockAppBuilder processName(String name) { + this.processName = name; when(app.getProcessName()).thenReturn(name); return this; } @@ -112,6 +116,11 @@ public class MockRunningAppProvider { return this; } + public MockAppBuilder getLiveConditionals(String autoConfigReport) throws Exception{ + when(app.getLiveConditionals()).thenReturn(SpringBootApp.getLiveConditionals(autoConfigReport, processId, processName)); + return this; + } + public MockAppBuilder profiles(String... names) { when(app.getActiveProfiles()).thenReturn(ImmutableList.copyOf(names)); return this; diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditional.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditional.java new file mode 100644 index 000000000..3f4432838 --- /dev/null +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditional.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.boot.app.cli; + +public class LiveConditional { + + private String condition; + private String message; + private String processId; + private String processName; + private String positiveMatchKey; + + public LiveConditional() { + + } + + public String getCondition() { + return condition; + } + + public String getMessage() { + return message; + } + + public String getProcessId() { + return processId; + } + + public String getProcessName() { + return processName; + } + + public String getPositiveMatchKey() { + return positiveMatchKey; + } + + public static class LiveConditionalBuilder { + + private LiveConditional conditional = new LiveConditional(); + + public LiveConditionalBuilder condition(String condition) { + conditional.condition = condition; + return this; + } + + public LiveConditionalBuilder message(String message) { + conditional.message = message; + return this; + } + + public LiveConditionalBuilder processId(String processId) { + conditional.processId = processId; + return this; + } + + public LiveConditionalBuilder processName(String processName) { + conditional.processName = processName; + return this; + } + + /** + * This is a JSON key in "positiveMatches" element in the autoconfig report that contains information regarding + * the method that the conditional is applied to. + * @param positiveMatchKey + * @return + */ + public LiveConditionalBuilder positiveMatchKey(String positiveMatchKey) { + conditional.positiveMatchKey = positiveMatchKey; + return this; + } + + public LiveConditional build() { + return conditional; + } + + } + + public static LiveConditionalBuilder builder() { + return new LiveConditionalBuilder(); + } +} \ No newline at end of file diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditionalParser.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditionalParser.java new file mode 100644 index 000000000..e871989fd --- /dev/null +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveConditionalParser.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.boot.app.cli; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; + +/** + * Parsers @ConditionalOn annotations from a spring boot running app's + * autoconfig report. An example of a conditional that is parsed from a running + * app's autoconfig report would be: + * + * {"TraceRepositoryAutoConfiguration#traceRepository":[{"condition":"OnBeanCondition","message":"@ConditionalOnMissingBean + * (types: org.springframework.boot.actuate.trace.TraceRepository; + * SearchStrategy: all) did not find any beans"}] + * + */ +public class LiveConditionalParser { + + private final String autoConfigRecord; + private final String appProcessName; + private final String appProcessId; + + public LiveConditionalParser(String autoConfigRecord, String appProcessId, String appProcessName) { + this.autoConfigRecord = autoConfigRecord; + this.appProcessId = appProcessId; + this.appProcessName = appProcessName; + } + + public Optional> parse() { + + try { + List allConditionals = new ArrayList<>(); + + if (StringUtil.hasText(autoConfigRecord)) { + getConditionalsFromPositiveMatches(autoConfigRecord).stream() + .forEach(conditional -> allConditionals.add(conditional)); + } + if (!allConditionals.isEmpty()) { + return Optional.of(allConditionals); + } + } catch (Exception e) { + Log.log(e); + } + return Optional.empty(); + } + + /** + * Fetches the "positiveMatches" element in the autoconfig report that contains conditional information. + * @param positiveMatchKey + * @return + */ + private Optional getPositiveMatchesJson(String autoConfigReport) { + JSONObject autoConfigJson = new JSONObject(autoConfigReport); + + Iterator keys = autoConfigJson.keys(); + + while (keys.hasNext()) { + String key = keys.next(); + if ("positiveMatches".equals(key)) { + Object obj = autoConfigJson.get(key); + if (obj instanceof JSONObject) { + return Optional.of((JSONObject) obj); + } + } + } + + return Optional.empty(); + } + + /** + * Fetches all the conditionals listed in the the "positiveMatches" element in the autoconfig report. + * + */ + private List getConditionalsFromPositiveMatches(String autoconfigReport) { + List conditions = new ArrayList<>(); + + getPositiveMatchesJson(autoconfigReport).ifPresent((positiveMatches) -> { + Iterator pMKeys = positiveMatches.keys(); + while (pMKeys.hasNext()) { + // The positive match key contains the bean method information where conditional + // was applied to + String positiveMatchKey = pMKeys.next(); + JSONArray matchList = (JSONArray) positiveMatches.get(positiveMatchKey); + matchList.forEach((match) -> { + if (match instanceof JSONObject) { + resolveConditional(positiveMatchKey, (JSONObject) match) + .ifPresent((condition) -> conditions.add(condition)); + } + }); + } + }); + + return conditions; + } + + private Optional resolveConditional(String positiveMatchKey, JSONObject conditionalJson) { + if (conditionalJson != null) { + String condition = (String) conditionalJson.get("condition"); + String message = (String) conditionalJson.get("message"); + // We care about the message itself as it contains the actual annotation as well + // as the reason it matched + if (StringUtil.hasText(message)) { + return Optional.of(LiveConditional.builder().processId(appProcessId).processName(appProcessName) + .condition(condition).message(message).positiveMatchKey(positiveMatchKey).build()); + } + } + return Optional.empty(); + } + + public static Optional> parse(String autoConfigRecord, String appProcessId, + String appProcessName) { + return new LiveConditionalParser(autoConfigRecord, appProcessId, appProcessName).parse(); + } +} diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index a6207d5dd..3a6daeaaa 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; +import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; @@ -235,6 +236,24 @@ public class SpringBootApp { return null; } + public Optional> getLiveConditionals() throws Exception { + return getLiveConditionals(getAutoConfigReport(), getProcessID(), getProcessName()); + } + + /** + * Publicly visible so that it can be tested via a mock app + * + * @param autoConfigReport + * @param processId + * @param processName + * @return + * @throws Exception + */ + public static Optional> getLiveConditionals(String autoConfigReport, String processId, + String processName) { + return new LiveConditionalParser(autoConfigReport, processId, processName).parse(); + } + public String getAutoConfigReport() throws Exception { Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=autoConfigurationReportEndpoint", "Data"); if (result != null) {