Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAMEL-21241: properties component - Should capture details about reso… #15625

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ public interface PropertiesComponent extends StaticService {
*/
Optional<String> resolveProperty(String key);

/**
* Returns metadata about a property which has successfully been resolved.
*
* @param key the name of the property
* @return the property value and metadata if present
*/
Optional<PropertiesResolvedValue> getResolvedValue(String key);

/**
* Loads the properties from the default locations and sources.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.spi;

/**
* Data about a {@link PropertiesComponent} property placeholder that has been resolved to a value by Camel.
*/
public record PropertiesResolvedValue(String name, String originalValue, String value, String defaultValue, String source) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@
import org.apache.camel.spi.LoadablePropertiesSource;
import org.apache.camel.spi.PropertiesSource;
import org.apache.camel.util.OrderedLocationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Default {@link PropertiesLookup} which lookup properties from a {@link java.util.Properties} with all existing
* properties.
*/
public class DefaultPropertiesLookup implements PropertiesLookup {

private static final Logger LOG = LoggerFactory.getLogger(DefaultPropertiesLookup.class);

private final PropertiesComponent component;

public DefaultPropertiesLookup(PropertiesComponent component) {
Expand All @@ -40,7 +44,9 @@ public DefaultPropertiesLookup(PropertiesComponent component) {
@Override
public String lookup(String name, String defaultValue) {
try {
return doLookup(name, defaultValue);
String answer = doLookup(name, defaultValue);
LOG.trace("lookup(name: {} default: {}) -> {}", name, defaultValue, answer);
return answer;
} catch (NoTypeConversionAvailableException e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}
Expand Down Expand Up @@ -119,6 +125,7 @@ private String doLookup(String name, String defaultValue) throws NoTypeConversio
}

private void onLookup(String name, String value, String defaultValue, String source) {
LOG.trace("Property (name: {} default: {}) resolved from source: {} -> {}", name, defaultValue, source, value);
for (PropertiesLookupListener listener : component.getPropertiesLookupListeners()) {
try {
listener.onLookup(name, value, defaultValue, source);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.properties;

import java.util.Map;

import org.apache.camel.PropertiesLookupListener;
import org.apache.camel.spi.PropertiesResolvedValue;
import org.apache.camel.support.LRUCacheFactory;
import org.apache.camel.support.service.ServiceSupport;

/**
* A {@link PropertiesLookupListener} listener that captures the resolved properties for dev consoles, management and
* troubleshooting purposes.
*/
public class DefaultPropertiesLookupListener extends ServiceSupport implements PropertiesLookupListener {

private Map<String, PropertiesResolvedValue> properties;

@Override
public void onLookup(String name, String value, String defaultValue, String source) {
properties.put(name, new PropertiesResolvedValue(name, value, value, defaultValue, source));
}

void updateValue(String name, String newValue, String newSource) {
var p = properties.get(name);
if (p != null) {
String source = newSource != null ? newSource : p.source();
properties.put(name, new PropertiesResolvedValue(p.name(), p.originalValue(), newValue, p.defaultValue(), source));
}
}

public PropertiesResolvedValue getProperty(String key) {
return properties.get(key);
}

@Override
protected void doBuild() throws Exception {
// use a cache with max limit to avoid capturing endless property values
// if there are a lot of dynamic values
properties = LRUCacheFactory.newLRUCache(1000);
}

@Override
protected void doShutdown() throws Exception {
properties.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public String parse(String input) {
input = input.replace("?nested=false", "");
}
if (nested) {
return doParseNested(input, new HashSet<>());
return doParseNested(null, input, new HashSet<>());
} else {
return doParse(input);
}
Expand All @@ -137,7 +137,8 @@ private String doParse(String input) {

StringBuilder answer = new StringBuilder(input.length());
Property property;
while ((property = readProperty(input)) != null) {
String prevKey = null;
while ((property = readProperty(prevKey, input)) != null) {
String before = input.substring(0, property.getBeginIndex());
String after = input.substring(property.getEndIndex());
String parsed = property.getValue();
Expand All @@ -149,6 +150,7 @@ private String doParse(String input) {
return null;
}
input = after;
prevKey = property.getKey();
}
if (!input.isEmpty()) {
answer.append(input);
Expand All @@ -163,13 +165,13 @@ private String doParse(String input) {
* @param replacedPropertyKeys Already replaced property keys used for tracking circular references
* @return Evaluated string
*/
private String doParseNested(String input, Set<String> replacedPropertyKeys) {
private String doParseNested(String prevKey, String input, Set<String> replacedPropertyKeys) {
if (input == null) {
return null;
}
String answer = input;
Property property;
while ((property = readProperty(answer)) != null) {
while ((property = readProperty(prevKey, answer)) != null) {
if (replacedPropertyKeys.contains(property.getKey())) {
// Check for circular references (skip optional)
boolean optional = property.getKey().startsWith(OPTIONAL_TOKEN);
Expand All @@ -181,6 +183,12 @@ private String doParseNested(String input, Set<String> replacedPropertyKeys) {
}
}

if (propertiesComponent != null) {
// nested placeholder so update resolved property with new value
String k = prevKey != null ? prevKey : property.getKey();
propertiesComponent.updateResolvedValue(k, property.getValue(), null);
}

Set<String> newReplaced = new HashSet<>(replacedPropertyKeys);
newReplaced.add(property.getKey());

Expand All @@ -191,7 +199,7 @@ private String doParseNested(String input, Set<String> replacedPropertyKeys) {
}
String before = answer.substring(0, beginIndex);
String after = answer.substring(property.getEndIndex());
String parsed = doParseNested(property.getValue(), newReplaced);
String parsed = doParseNested(property.getKey(), property.getValue(), newReplaced);
if (parsed != null) {
answer = before + parsed + after;
} else {
Expand All @@ -213,7 +221,7 @@ private String doParseNested(String input, Set<String> replacedPropertyKeys) {
* @param input Input string
* @return A property in the given string or {@code null} if not found
*/
private Property readProperty(String input) {
private Property readProperty(String prevKey, String input) {
// Find the index of the first valid suffix token
int suffix = getSuffixIndex(input);

Expand All @@ -232,7 +240,7 @@ private Property readProperty(String input) {
}

String key = input.substring(prefix + PREFIX_TOKEN.length(), suffix);
String value = getPropertyValue(key, input);
String value = getPropertyValue(prevKey, key, input);
return new Property(prefix, suffix + SUFFIX_TOKEN.length(), key, value);
}

Expand Down Expand Up @@ -308,7 +316,7 @@ private boolean isEscaped(String input, int index) {
* @param input Input string (used for exception message if value not found)
* @return Value of the property with the given key
*/
private String getPropertyValue(String key, String input) {
private String getPropertyValue(String prevKey, String key, String input) {
if (key == null) {
return null;
}
Expand All @@ -326,7 +334,7 @@ private String getPropertyValue(String key, String input) {
String remainder = StringHelper.after(key, ":");
boolean remainderOptional = remainder.startsWith(OPTIONAL_TOKEN);
if (function.lookupFirst(remainder)) {
String value = getPropertyValue(remainder, input);
String value = getPropertyValue(prevKey, remainder, input);
if (value == null && (remainderOptional || function.optional(remainder))) {
return null;
}
Expand Down Expand Up @@ -366,6 +374,8 @@ private String getPropertyValue(String key, String input) {
log.debug("Property with key [{}] applied by function [{}] -> {}", key, function.getName(),
value);
}
String k = prevKey != null ? prevKey : key;
propertiesComponent.updateResolvedValue(k, value, function.getName());
return value;
}
}
Expand All @@ -382,6 +392,13 @@ private String getPropertyValue(String key, String input) {
if (value == null && defaultValue != null) {
log.debug("Property with key [{}] not found, using default value: {}", key, defaultValue);
value = defaultValue;
for (PropertiesLookupListener listener : propertiesComponent.getPropertiesLookupListeners()) {
try {
listener.onLookup(key, value, defaultValue, null);
} catch (Exception e) {
// ignore
}
}
}

if (value == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.apache.camel.spi.FactoryFinder;
import org.apache.camel.spi.LoadablePropertiesSource;
import org.apache.camel.spi.PropertiesFunction;
import org.apache.camel.spi.PropertiesResolvedValue;
import org.apache.camel.spi.PropertiesSource;
import org.apache.camel.spi.PropertiesSourceFactory;
import org.apache.camel.spi.annotations.JdkService;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class PropertiesComponent extends ServiceSupport
private final PropertiesLookup propertiesLookup = new DefaultPropertiesLookup(this);
private final List<PropertiesLookupListener> propertiesLookupListeners = new ArrayList<>();
private final PropertiesSourceFactory propertiesSourceFactory = new DefaultPropertiesSourceFactory(this);
private final DefaultPropertiesLookupListener defaultPropertiesLookupListener = new DefaultPropertiesLookupListener();
private final List<PropertiesSource> sources = new ArrayList<>();
private List<PropertiesLocation> locations = new ArrayList<>();
private String location;
Expand All @@ -128,6 +130,7 @@ public class PropertiesComponent extends ServiceSupport
private boolean autoDiscoverPropertiesSources = true;

public PropertiesComponent() {
addPropertiesLookupListener(defaultPropertiesLookupListener);
// include out of the box functions
addPropertiesFunction(new EnvPropertiesFunction());
addPropertiesFunction(new SysPropertiesFunction());
Expand Down Expand Up @@ -187,6 +190,15 @@ public Optional<String> resolveProperty(String key) {
}
}

@Override
public Optional<PropertiesResolvedValue> getResolvedValue(String key) {
return Optional.ofNullable(defaultPropertiesLookupListener.getProperty(key));
}

public void updateResolvedValue(String key, String newValue, String newSource) {
defaultPropertiesLookupListener.updateValue(key, newValue, newSource);
}

@Override
public Properties loadProperties() {
// this method may be replaced by loadProperties(k -> true) but the underlying sources
Expand Down Expand Up @@ -784,27 +796,27 @@ protected void doInit() throws Exception {
}

sources.sort(OrderedComparator.get());
ServiceHelper.initService(sources, propertiesFunctionResolver);
ServiceHelper.initService(sources, propertiesFunctionResolver, defaultPropertiesLookupListener);
}

@Override
protected void doBuild() throws Exception {
ServiceHelper.buildService(sources, propertiesFunctionResolver);
ServiceHelper.buildService(sources, propertiesFunctionResolver, defaultPropertiesLookupListener);
}

@Override
protected void doStart() throws Exception {
ServiceHelper.startService(sources, propertiesFunctionResolver);
ServiceHelper.startService(sources, propertiesFunctionResolver, defaultPropertiesLookupListener);
}

@Override
protected void doStop() throws Exception {
ServiceHelper.stopService(sources, propertiesFunctionResolver);
ServiceHelper.stopService(sources, propertiesFunctionResolver, defaultPropertiesLookupListener);
}

@Override
protected void doShutdown() throws Exception {
ServiceHelper.stopAndShutdownServices(sources, propertiesFunctionResolver);
ServiceHelper.stopAndShutdownServices(sources, propertiesFunctionResolver, defaultPropertiesLookupListener);
}

private void addPropertiesLocationsAsPropertiesSource(PropertiesLocation location, int order) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,28 @@ protected JsonObject doCallJson(Map<String, Object> options) {
String k = entry.getKey().toString();
Object v = entry.getValue();
String loc = olp != null ? olp.getLocation(k) : null;

String originalValue = null;
String defaultValue = null;
String source = null;
var m = pc.getResolvedValue(k);
if (m.isPresent()) {
originalValue = m.get().originalValue();
defaultValue = m.get().defaultValue();
source = m.get().source();
v = m.get().value();
}
JsonObject jo = new JsonObject();
jo.put("key", k);
jo.put("value", v);
if (originalValue != null) {
jo.put("originalValue", originalValue);
}
if (defaultValue != null) {
jo.put("defaultValue", defaultValue);
}
if (source != null) {
jo.put("source", source);
}
if (loc != null) {
jo.put("location", loc);
jo.put("internal", isInternal(loc));
Expand Down
Loading