-
Notifications
You must be signed in to change notification settings - Fork 40.8k
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
Deprecate environment fallback for Mustache variable resolution #21045
Comments
When the context is a map (as it is in a web View for instance) you can't assume a non-null fetcher actually contains the property you are searching for. This change alters the logic so that the native fetcher is always consulted if it exists, but there is always a fallback. Fixes spring-projectsgh-21045
When the context is a map (as it is in a web View for instance) you can't assume a non-null fetcher actually contains the property you are searching for. This change alters the logic so that the native fetcher is always consulted if it exists, but there is always a fallback. Fixes spring-projectsgh-21045
Closing in favor of PR #21060 |
Unfortunately, it's flawed to assume that a |
The more I think about this, the less I am sure we should change the behaviour here. If the context contains a |
Can we have a debate about this please? The (reasonable) expectation from users is that they can put placeholders in templates for values in the |
I agree that's a reasonable expectation, and it's one that, AIUI, is already supported. As long as the model doesn't contain a value (including null) for a placeholder, the environment will be queried. If the user's put a |
Looking again, the current tests are wrong and falling back to the environment doesn't always work, irrespective of whether a name is mapped to |
Some of the tests in the reverted PR seem to me to be wrong as well. They assert that the model takes priority for "normal" keys but that the environment takes priority for compound keys. For example, if |
I've reworked the tests to illustrate some of the current inconsistencies with how object- and map-based models are handled. There's one test that fails with both models and 7 that fail with only one type of model. Mustache's handling of compound keys complicates things. When it's in standards mode, resolving The problem with the above is that the collector needs to know if a compound key will result in a single call (standards mode) or multiple calls (the default), falling back to the environment when the first and only call finds nothing or once the key contains no |
We might be able to have the collector make an assumption about the mode that Mustache is using. When we auto-configure Mustache the compiler doesn't have standards mode enabled so the collector that we auto-configure could know this. If the user replaces the compiler with one with standards mode enabled, they could configure a collector with the same mode. |
Does this mean the current state of things is that values from environment are not supported, if any kind of model map exists? Currently in my app I'm not able to pass environment variables to be used by jmustache, and I'm not sure if that's how it is intended, this issue discussed here, or a problem with my configuration. |
That sounds like the issue discussed here. |
Mustache doesn't behave as I had hoped above. When it's not in standards mode and
Once the third call hasn't found anything, we need to fall back to the environment and get the As far as I can tell, it isn't possible to consistently fall back to the environment when using Mustache in non-standards mode. |
I think we're going to need to find another way to skin this cat. The most straightforward would be to recommend that users consider the environment when creating the model that's passed to Mustache if that's the behaviour that a they want. When creating a |
@dsyer Unless you can think of something that I've missed, we're going to remove the environment fallback in 2.5 as it doesn't seem possible to make it work in all scenarios. |
We'd have to duplicate some of the logic in the Mustache public class MustacheEnvironmentCollector extends DefaultCollector implements EnvironmentAware {
private ConfigurableEnvironment environment;
private final VariableFetcher propertyFetcher = new PropertyVariableFetcher();
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
@Override
public VariableFetcher createFetcher(Object ctx, String name) {
VariableFetcher fetcher = super.createFetcher(ctx, name);
Object value = null;
if (fetcher != null) {
try {
value = fetcher.get(ctx, name);
}
catch (Exception e) {
}
if (value != null && !Template.NO_FETCHER_FOUND.equals(value)) {
return fetcher;
}
}
if (this.environment.containsProperty(name)) {
Object compound = getCompoundValue(ctx, name);
// There's a TODO in the tests related to this null check
if (compound == null || Template.NO_FETCHER_FOUND.equals(compound)) {
return this.propertyFetcher;
}
}
return fetcher;
}
private Object getCompoundValue(Object ctx, String name) {
if (!name.contains(".")) {
return Template.NO_FETCHER_FOUND;
}
String[] comps = name.split("\\.");
Object value = null;
int index = 0;
while (index < comps.length && !Template.NO_FETCHER_FOUND.equals(value)) {
VariableFetcher fetcher = super.createFetcher(ctx, comps[index]);
if (fetcher == null) {
value = Template.NO_FETCHER_FOUND;
break;
}
try {
value = fetcher.get(ctx, comps[index]);
}
catch (Exception e) {
value = Template.NO_FETCHER_FOUND;
break;
}
ctx = value;
index++;
}
return value;
}
private class PropertyVariableFetcher implements VariableFetcher {
@Override
public Object get(Object ctx, String name) {
return MustacheEnvironmentCollector.this.environment.getProperty(name);
}
}
} |
Thanks, Dave. I didn't give much consideration to any approaches that would require duplicating lots of Mustache's logic. My concern is that our copy of the logic will get out of sync with Mustache's. I think I'd rather drop the feature than introduce that fragility, but let's see what the rest of the team thinks. |
FWIW that code is super stable. And all we really did was borrow it to suit our purpose, so even if it changed in Mustache, it would arguably still suit our purpose, so there would be no reason to change it, unless they changed the way |
We discussed this on Friday and decided that we're going to deprecate support for the environment fallback. Beyond it not working correctly in all scenarios, we think that it should be something that users opt into by populating the model with values read from the environment. This will make Mustache's behaviour consistent with the other template engines that we auto-configure where, for example, FreeMarker's |
As far as I can see, the current fallback behaviour isn't documented so there's not much to document here, in the reference documentation at least. I think it's still worth mentioning the change in the upgrade section of the release notes though. |
MustacheEnvironmentCollector
has some strange logic in it (which I probably wrote). It looks for the "native" collector viasuper.createFetcher()
but then uses it exclusively if it is not null. But a not-null fetcher is not the same as a fetcher of not-null values. What it should do is inject the fetcher into thePropertyVariableFetcher
and try to use it before falling back to theEnvironment
.There's a complication though, to do with the fact that JMustache natively resolves variables with period separators as
bean.property
(i.e. "bean" is an object). We wouldn't want to break that, but we don't want to start returning random stuff from theEnvironment
that happens to matchbean.property
. It might turn out that this concern is already handled by JMustache (it depends on the order it presents the variable names to the fetcher - does it trybean
beforebean.property
or the other way round?). Need a test.The text was updated successfully, but these errors were encountered: