/** * Retrieve detailed information about a particular application. * @param type application type * @param name application name * @return detailed application information */ @RequestMapping(value = "/{type}/{name}", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) public DetailedAppRegistrationResource info( @PathVariable("type") String type, @PathVariable("name") String name) { AppRegistration registration = appRegistry.find(name, type); if (registration == null) { throw new NoSuchAppRegistrationException(name, type); } DetailedAppRegistrationResource result = new DetailedAppRegistrationResource(assembler.toResource(registration)); Resource resource = registration.getResource(); List<ConfigurationMetadataProperty> properties = metadataResolver.listProperties(resource); for (ConfigurationMetadataProperty property : properties) { result.addOption(property); } return result; }
private void completePropName(SpringBootService sbs, CompletionResultSet completionResultSet, String filter, int startOffset, int caretOffset) { final Preferences prefs = NbPreferences.forModule(PrefConstants.class); final boolean bDeprLast = prefs.getBoolean(PREF_DEPR_SORT_LAST, true); final boolean bErrorShow = prefs.getBoolean(PREF_DEPR_ERROR_SHOW, true); long mark = System.currentTimeMillis(); logger.log(FINER, "Completing property name: {0}", filter); for (ConfigurationMetadataProperty propMeta : sbs.queryPropertyMetadata(filter)) { if (Utils.isErrorDeprecated(propMeta)) { // show error level deprecated props based on pref if (bErrorShow) { completionResultSet.addItem(new CfgPropCompletionItem(propMeta, sbs, startOffset, caretOffset, bDeprLast)); } } else { completionResultSet.addItem(new CfgPropCompletionItem(propMeta, sbs, startOffset, caretOffset, bDeprLast)); } } final long elapsed = System.currentTimeMillis() - mark; logger.log(FINER, "Property completion of ''{0}'' took: {1} msecs", new Object[]{filter, elapsed}); }
private void filterProps(String filter) { DefaultListModel<ConfigurationMetadataProperty> dlmCfgProps = new DefaultListModel<>(); for (ConfigurationMetadataProperty item : sortedProps) { if (filter == null || item.getId().contains(filter)) { if (Utils.isErrorDeprecated(item)) { if (bDeprErrorShow) { dlmCfgProps.addElement(item); } } else { dlmCfgProps.addElement(item); } } } lCfgProps.setModel(dlmCfgProps); if (!dlmCfgProps.isEmpty()) { lCfgProps.setSelectedIndex(0); } }
@Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value instanceof ConfigurationMetadataProperty) { ConfigurationMetadataProperty prop = (ConfigurationMetadataProperty) value; if (prop.isDeprecated()) { setText(String.format("<html><s>%s", prop.getId())); if (Utils.isErrorDeprecated(prop)) { setForeground(UIManager.getColor("nb.errorForeground")); } } else { setText(prop.getId()); } } return this; }
@Override public int compare(ConfigurationMetadataProperty p1, ConfigurationMetadataProperty p2) { if (!sortDeprLast) { return p1.getId().compareTo(p2.getId()); } else { boolean d1 = p1.isDeprecated(); boolean d2 = p2.isDeprecated(); if (d1 && !d2) { return 1; } else if (d2 && !d1) { return -1; } else { return p1.getId().compareTo(p2.getId()); } } }
@Override public List<ValueHint> queryHintMetadata(String propertyName, String filter) { if (cpExec == null) { init(); } List<ValueHint> ret = new LinkedList<>(); ConfigurationMetadataProperty cfgMeta = getPropertyMetadata(propertyName); if (cfgMeta != null) { for (ValueHint valueHint : cfgMeta.getHints().getValueHints()) { if (filter == null || valueHint.getValue().toString().contains(filter)) { ret.add(valueHint); } } } return ret; }
void addValueHintsProposals(final String dsl, AppRegistration appRegistration, final List<CompletionProposal> collector, final String propertyName, final ValueHintProvider[] valueHintProviders){ final Resource metadataResource = this.appRegistry.getAppMetadataResource(appRegistration); if (metadataResource != null) { final URLClassLoader classLoader = metadataResolver.createAppClassLoader(metadataResource); this.doWithClassLoader(classLoader, () -> { CompletionProposal.Factory proposals = expanding(dsl); List<ConfigurationMetadataProperty> whiteList = metadataResolver.listProperties(metadataResource); for (ConfigurationMetadataProperty property : metadataResolver.listProperties(metadataResource, true)) { if (CompletionUtils.isMatchingProperty(propertyName, property, whiteList)) { for (ValueHintProvider valueHintProvider : valueHintProviders) { for (ValueHint valueHint : valueHintProvider.generateValueHints(property, classLoader)) { collector.add(proposals.withSuffix(String.valueOf(valueHint.getValue()), valueHint.getShortDescription())); } } } } return null; }); } }
/** * Return whether the given property name should be considered matching the candidate * configuration property, also taking into account the list of whitelist properties * (which are tested on their short name). */ static boolean isMatchingProperty(String propertyName, ConfigurationMetadataProperty property, List<ConfigurationMetadataProperty> whiteListedProps) { if (property.getId().equals(propertyName)) { return true; // For any prop } // Handle special case of short form for whitelist else { for (ConfigurationMetadataProperty white : whiteListedProps) { if (property.getId().equals(white.getId())) { // prop#equals() not // implemented return property.getName().equals(propertyName); } } return false; } }
/** * Retrieve detailed information about a particular application. * * @param type application type * @param name application name * @param version application version * @return detailed application information */ @RequestMapping(value = "/{type}/{name}/{version:.+}", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) public DetailedAppRegistrationResource info(@PathVariable("type") ApplicationType type, @PathVariable("name") String name, @PathVariable("version") String version) { AppRegistration registration = appRegistryService.find(name, type, version); if (registration == null) { throw new NoSuchAppRegistrationException(name, type, version); } DetailedAppRegistrationResource result = new DetailedAppRegistrationResource( assembler.toResource(registration)); List<ConfigurationMetadataProperty> properties = metadataResolver .listProperties(appRegistryService.getAppMetadataResource(registration)); for (ConfigurationMetadataProperty property : properties) { result.addOption(property); } return result; }
/** * Retrieve detailed information about a particular application. * * @param type application type * @param name application name * @return detailed application information */ @RequestMapping(value = "/{type}/{name}", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) public DetailedAppRegistrationResource info(@PathVariable("type") ApplicationType type, @PathVariable("name") String name) { AppRegistration registration = appRegistry.find(name, type); if (registration == null) { throw new NoSuchAppRegistrationException(name, type); } DetailedAppRegistrationResource result = new DetailedAppRegistrationResource( assembler.toResource(registration)); List<ConfigurationMetadataProperty> properties = metadataResolver .listProperties(appRegistry.getAppMetadataResource(registration)); for (ConfigurationMetadataProperty property : properties) { result.addOption(property); } return result; }
/** * Return metadata about configuration properties that are documented via * <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html"> * Spring Boot configuration metadata</a> and visible in an app. * @param app a Spring Cloud Stream app; typically a Boot uberjar, * but directories are supported as well */ public List<ConfigurationMetadataProperty> listProperties(Resource app, boolean exhaustive) { try { Archive archive = resolveAsArchive(app); return listProperties(archive, exhaustive); } catch (IOException e) { throw new RuntimeException("Failed to list properties for " + app, e); } }
@Override public List<ConfigurationMetadataProperty> listProperties(Resource app, boolean exhaustive) { for (ApplicationConfigurationMetadataResolver delegate : delegates) { if (delegate.supports(app)) { return delegate.listProperties(app, exhaustive); } } return Collections.emptyList(); }
@Test public void otherPropertiesShouldOnlyBeVisibleInExtensiveCall() { List<ConfigurationMetadataProperty> properties = resolver.listProperties(new ClassPathResource("apps/filter-processor", getClass())); assertThat(properties, not(hasItem(configPropertyIdentifiedAs("some.prefix.hidden.by.default.secret")))); properties = resolver.listProperties(new ClassPathResource("apps/filter-processor", getClass()), true); assertThat(properties, hasItem(configPropertyIdentifiedAs("some.prefix.hidden.by.default.secret"))); }
@Test public void shouldReturnEverythingWhenNoDescriptors() { List<ConfigurationMetadataProperty> properties = resolver.listProperties(new ClassPathResource("apps/no-whitelist", getClass())); List<ConfigurationMetadataProperty> full = resolver.listProperties(new ClassPathResource("apps/no-whitelist", getClass()), true); assertThat( properties.size(), greaterThan(0)); assertThat(properties.size(), is(full.size())); }
/** * Escapes some special values so that they don't disturb console * rendering and are easier to read. */ private String prettyPrintDefaultValue(ConfigurationMetadataProperty o) { if (o.getDefaultValue() == null) { return "<none>"; } return o.getDefaultValue().toString() .replace("\n", "\\n") .replace("\t", "\\t") .replace("\f", "\\f"); }
/** * Return whether the given property name should be considered matching the candidate configuration property, also * taking into account the list of whitelist properties (which are tested on their short name). */ static boolean isMatchingProperty(String propertyName, ConfigurationMetadataProperty property, List<ConfigurationMetadataProperty> whiteListedProps) { if (property.getId().equals(propertyName)) { return true; // For any prop } // Handle special case of short form for whitelist else { for (ConfigurationMetadataProperty white : whiteListedProps) { if (property.getId().equals(white.getId())) { // prop#equals() not implemented return property.getName().equals(propertyName); } } return false; } }
@Override public boolean isExclusive(ConfigurationMetadataProperty property) { for (ValueProvider valueProvider : property.getValueProviders()) { if ("any".equals(valueProvider.getName())) { return false; } } return true; }
@Override public List<ValueHint> generateValueHints(ConfigurationMetadataProperty property, ClassLoader classLoader) { List<ValueHint> result = new ArrayList<>(); if (ClassUtils.isPresent(property.getType(), classLoader)) { Class<?> clazz = ClassUtils.resolveClassName(property.getType(), classLoader); if (clazz.isEnum()) { for (Object o : clazz.getEnumConstants()) { ValueHint hint = new ValueHint(); hint.setValue(o); result.add(hint); } } } return result; }
private void assertNoAmbiguity(List<ConfigurationMetadataProperty> longForms) { if (longForms.size() > 1) { Set<String> ids = new HashSet<>(longForms.size()); for (ConfigurationMetadataProperty pty : longForms) { ids.add(pty.getId()); } throw new IllegalArgumentException(String.format("Ambiguous short form property '%s' could mean any of %s", longForms.iterator().next().getName(), ids)); } }
public CfgPropCompletionItem(ConfigurationMetadataProperty configurationMeta, SpringBootService bootService, int propStartOffset, int caretOffset, boolean sortDeprLast) { this.overwrite = false; this.configurationMeta = configurationMeta; if (configurationMeta.getType() != null) { type = simpleHtmlEscape(shortenJavaType(configurationMeta.getType())); } else { type = null; } this.bootService = bootService; this.propStartOffset = propStartOffset; this.caretOffset = caretOffset; this.sortDeprLast = sortDeprLast; }
@Override protected void internalRun(CfgPropsParser.CfgPropsParserResult cfgResult, SchedulerEvent se, Document document, List<ErrorDescription> errors, Severity severity) { logger.fine("Highlighting unknown properties"); final Project prj = Utilities.actionsGlobalContext().lookup(Project.class); if (prj != null) { final SpringBootService sbs = prj.getLookup().lookup(SpringBootService.class); if (sbs != null) { for (PairElement pair : cfgResult.getCfgFile().getElements()) { final CfgElement key = pair.getKey(); final CfgElement value = pair.getValue(); final String pName = key.getText(); ConfigurationMetadataProperty cfgMeta = sbs.getPropertyMetadata(pName); if (cfgMeta == null) { try { List<Fix> fixes = new ArrayList<>(); int end = value != null ? value.getIdxEnd() : key.getIdxEnd(); fixes.add(new DeletePropFix((StyledDocument) document, key.getText(), key.getIdxStart(), end)); ErrorDescription errDesc = ErrorDescriptionFactory.createErrorDescription( severity, String.format("Unknown Spring Boot property '%s'", pName), fixes, document, document.createPosition(key.getIdxStart()), document.createPosition(key.getIdxEnd()) ); errors.add(errDesc); } catch (BadLocationException ex) { Exceptions.printStackTrace(ex); } } if (canceled) { break; } } } } if (!errors.isEmpty()) { logger.log(Level.FINE, "Found {0} unknown properties", errors.size()); } }
/** Creates new form CfgPropsDialog */ public CfgPropsDialog(java.awt.Dialog parent) { super(parent, true); initComponents(); // retrieve some flads from prefs final Preferences prefs = NbPreferences.forModule(PrefConstants.class); final boolean bDeprLast = prefs.getBoolean(PREF_DEPR_SORT_LAST, true); bDeprErrorShow = prefs.getBoolean(PREF_DEPR_ERROR_SHOW, true); // setup props sorting this.sortedProps = new TreeSet<>(new ConfigurationMetadataComparator(bDeprLast)); // setup property list lCfgProps.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { final ConfigurationMetadataProperty selectedValue = lCfgProps.getSelectedValue(); if (selectedValue != null) { tpDetails.setText(Utils.cfgPropDetailsHtml(selectedValue)); tpDetails.setCaretPosition(0); } } } }); // set default button rootPane.setDefaultButton(bOk); // close dialog with ESC key final ActionListener escAction = new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { CfgPropsDialog.this.setVisible(false); } }; rootPane.registerKeyboardAction(escAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); }
@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 = pArrayNotation.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<ConfigurationMetadataProperty> queryPropertyMetadata(String filter) { if (cpExec == null) { init(); } List<ConfigurationMetadataProperty> ret = new LinkedList<>(); if (cachedProperties != null) { for (String propName : getPropertyNames()) { if (filter == null || propName.contains(filter)) { ret.add(cachedProperties.get(propName)); } } } return ret; }
/** * Builds an HTML formatted string with details on a Spring Boot configuration property extracted from its {@code ItemMetadata}. * * @param cfgMeta the configuration property metadata object * @return the HTML formatted configuration property details */ public static String cfgPropDetailsHtml(ConfigurationMetadataProperty cfgMeta) { StringBuilder sb = new StringBuilder(); // deprecation (optional) Deprecation deprecation = cfgMeta.getDeprecation(); if (deprecation != null) { sb.append("<b>"); if (isErrorDeprecated(cfgMeta)) { sb.append("REMOVED"); } else { sb.append("Deprecated"); } sb.append("</b>"); // deprecation reason if present String reason = deprecation.getReason(); if (reason != null) { sb.append(": ").append(simpleHtmlEscape(reason)); } sb.append("<br/>"); String replacement = deprecation.getReplacement(); if (replacement != null) { sb.append("<i>Replaced by:</i> <tt>").append(replacement).append("</tt><br/>"); } } // description (optional) final String description = cfgMeta.getDescription(); if (description != null) { sb.append(description).append("<br/>"); } // type sb.append("<tt>").append(simpleHtmlEscape(shortenJavaType(cfgMeta.getType()))).append("</tt>"); return sb.toString(); }
private String asciidocFor(ConfigurationMetadataProperty property, ClassLoader classLoader) { return String.format("$$%s$$:: $$%s$$ *($$%s$$, default: `$$%s$$`%s)*", property.getId(), niceDescription(property), niceType(property), niceDefault(property), maybeHints(property, classLoader)); }
private CharSequence maybeHints(ConfigurationMetadataProperty property, ClassLoader classLoader) { String type = property.getType().replace('$', '.'); if (ClassUtils.isPresent(type, classLoader)) { Class<?> clazz = ClassUtils.resolveClassName(type, classLoader); if (clazz.isEnum()) { return ", possible values: `" + StringUtils.arrayToDelimitedString(clazz.getEnumConstants(), "`,`") + "`"; } } return ""; }
private String niceDefault(ConfigurationMetadataProperty property) { if (property.getDefaultValue() == null) { return "<none>"; } else if ("".equals(property.getDefaultValue())) { return "<empty string>"; } else { return stringify(property.getDefaultValue()); } }
private String niceType(ConfigurationMetadataProperty property) { String type = property.getType(); if (type == null) { return "<unknown>"; } int lastDot = type.lastIndexOf('.'); int lastDollar = type.lastIndexOf('$'); boolean hasGenerics = type.contains("<"); return hasGenerics ? type : type.substring(Math.max(lastDot, lastDollar) + 1); }
/** * Escapes some special values so that they don't disturb console rendering and are easier * to read. */ protected String prettyPrintDefaultValue(ConfigurationMetadataProperty o) { if (o.getDefaultValue() == null) { return "<none>"; } return o.getDefaultValue().toString().replace("\n", "\\n").replace("\t", "\\t").replace("\f", "\\f"); }
/** * Return metadata about configuration properties that are documented via <a href= * "http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html"> * Spring Boot configuration metadata</a> and visible in an app. * * @param app a Spring Cloud Stream app; typically a Boot uberjar, but directories are * supported as well */ public List<ConfigurationMetadataProperty> listProperties(Resource app, boolean exhaustive) { try { if (app != null) { Archive archive = resolveAsArchive(app); return listProperties(archive, exhaustive); } } catch (IOException e) { } return Collections.emptyList(); }
@Test public void appSpecificWhitelistedPropsShouldBeVisible() { List<ConfigurationMetadataProperty> properties = resolver .listProperties(new ClassPathResource("apps/filter-processor", getClass())); assertThat(properties, hasItem(configPropertyIdentifiedAs("filter.expression"))); assertThat(properties, hasItem(configPropertyIdentifiedAs("some.other.property.whitelisted.prefix.expresso2"))); }
@Test public void otherPropertiesShouldOnlyBeVisibleInExtensiveCall() { List<ConfigurationMetadataProperty> properties = resolver .listProperties(new ClassPathResource("apps/filter-processor", getClass())); assertThat(properties, not(hasItem(configPropertyIdentifiedAs("some.prefix.hidden.by.default.secret")))); properties = resolver.listProperties(new ClassPathResource("apps/filter-processor", getClass()), true); assertThat(properties, hasItem(configPropertyIdentifiedAs("some.prefix.hidden.by.default.secret"))); }
@Test public void shouldReturnEverythingWhenNoDescriptors() { List<ConfigurationMetadataProperty> properties = resolver .listProperties(new ClassPathResource("apps/no-whitelist", getClass())); List<ConfigurationMetadataProperty> full = resolver .listProperties(new ClassPathResource("apps/no-whitelist", getClass()), true); assertThat(properties.size(), greaterThan(0)); assertThat(properties.size(), is(full.size())); }
boolean addAlreadyTypedValueHintsProposals(final String text, AppRegistration appRegistration, final List<CompletionProposal> collector, final String propertyName, final ValueHintProvider[] valueHintProviders, final String alreadyTyped){ final Resource metadataResource = this.appRegistry.getAppMetadataResource(appRegistration); if (metadataResource == null) { return false; } final URLClassLoader classLoader = metadataResolver.createAppClassLoader(metadataResource); return this.doWithClassLoader(classLoader, () -> { CompletionProposal.Factory proposals = expanding(text); List<ConfigurationMetadataProperty> allProps = metadataResolver.listProperties(metadataResource, true); List<ConfigurationMetadataProperty> whiteListedProps = metadataResolver.listProperties(metadataResource); for (ConfigurationMetadataProperty property : allProps) { if (CompletionUtils.isMatchingProperty(propertyName, property, whiteListedProps)) { for (ValueHintProvider valueHintProvider : valueHintProviders) { List<ValueHint> valueHints = valueHintProvider.generateValueHints(property, classLoader); if (!valueHints.isEmpty() && valueHintProvider.isExclusive(property)) { collector.clear(); } for (ValueHint valueHint : valueHints) { String candidate = String.valueOf(valueHint.getValue()); if (!candidate.equals(alreadyTyped) && candidate.startsWith(alreadyTyped)) { collector.add(proposals.withSuffix(candidate.substring(alreadyTyped.length()), valueHint.getShortDescription())); } } if (!valueHints.isEmpty() && valueHintProvider.isExclusive(property)) { return true; } } } } return false; }); }
@Override public boolean isExclusive(ConfigurationMetadataProperty property) { for (ValueProvider valueProvider : property.getHints().getValueProviders()) { if ("any".equals(valueProvider.getName())) { return false; } } return true; }
@Test public void writeMetadataInfo() throws Exception { InputStream inputStream = new FileInputStream( "target/classes/META-INF/spring-configuration-metadata.json"); ConfigurationMetadataRepository repository = ConfigurationMetadataRepositoryJsonBuilder .create(UTF_8).withJsonResource(inputStream).build(); for (Map.Entry<String, ConfigurationMetadataProperty> entry : repository .getAllProperties().entrySet()) { System.out.println( entry.getKey() + " = " + entry.getValue().getShortDescription()); } }
/** * Return whether a single property has been white listed as being a "main" configuration property. */ private boolean isWhiteListed(ConfigurationMetadataProperty property, Collection<String> properties) { return properties.contains(property.getId()); }