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

Errors in EL expressions in User Guide [SPR-5847] #10516

Closed
spring-issuemaster opened this issue Jun 20, 2009 · 16 comments
Assignees
Milestone

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Jun 20, 2009

Dave Syer opened SPR-5847 and commented

Errors in EL expressions in User Guide. It would be nice if properties could be de-referenced as in the guide, e.g.

#{jdbcProperties.username}

but empirically it only seems to work as

#{jdbcProperties['username']}

If EL is supposed to support the first example then I did something wrong? It would be nice if it did support that (consistent with other similar idioms e.g. in Groovy), and also jdbcProperties[username].


Affects: 3.0 M3

Issue Links:

  • #20447 Variable index treated as string literal

Referenced from: commits dbdac9f, a4b7ce1

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 6, 2009

Andy Clement commented

What is your jdbcProperties object? If it is a Map then 'jdbcProperties.username' works if a MapAccessor is registered:

StandardEvaluationContext eContext = new StandardEvaluationContext(new TestProperties2());
Expression expr = new SpelExpressionParser().parse("jdbcProperties.username");
eContext.addPropertyAccessor(new MapAccessor()); // org.springframework.context.expression.MapAccessor
String name = expr.getValue(eContext,String.class);
Assert.assertEquals("Dave",name);

A MapAccessor is registered automatically if the EL is used within Spring (I believe), but if the EL is used standalone then a map accessor is not registered by default. We could change this.

The second case of 'jdbcProperties['username']' does not currently work. It could be made to work but I think addressing that is actually a separate issue.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 6, 2009

Dave Syer commented

I think you must be wrong about the way that Spring sets up the EL parser in an ApplicationContext. The first example (as per user guide) gives me a nice fat exception:

...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 17): Field or property 'username' cannot be found on object of type 'java.util.Properties'
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:138)
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:56)
	at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:57)
	at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:79)
	at org.springframework.expression.spel.SpelExpression.getValue(SpelExpression.java:67)
	at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:125)
	... 42 more

The second example works. I'm using last night's snapshot.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 6, 2009

Andy Clement commented

I did write that first comment in a bit of a hurry. Let me slow down a bit.

"jdbcProperties['username']"
that works fine.

jdbcProperties.username
that does not work without a MapAccessor registered. I don't typically run EL myself as setup through the ApplicationContext - I suspected it would work because Juergen wrote a MapAccessor that is registered within the StandardBeanExpressionResolver.evaluate() method. I'll work up a testcase and try it out.

My final mistake was saying 'jdbcProperties['username']' does not currently work, I meant "jdbcProperties[username]". I'm really in two minds about whether to make it work. An unqualified reference like username in that expression is evaluated as a property of the current active context object (which in this case is whatever you are evaluating jdbcProperties against). I could special case when it is wrapped in a [] and that is being used on a map. If you think it should work, I can make it work that way, but is it such a problem that you need the quotes?

All the documentation for the EL, apart from the section on how spring framework exploits the EL, has been converted to test programs (or at least I did a pass of it with the 3.0M3 version of the docs). Guess I should have done that for the spring framework section too!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 6, 2009

Andy Clement commented

I'm having more success writing tests around that 3rd scenario "jdbcProperties[username]" than application context tests for "jdbcProperties.username" so I'm looking more at that case. As I said, unqualified references are resolved against the current active context object.

If we evaluate the expression "jdbcProperties[username]" then first jdbcProperties is first processed, the current context object becomes a Properties object. Then the unqualified reference "username" is resolved against that - this actually finds a value, and evaluates to what you really wanted as the return value, ie. 'Dave' - but then we actually evaluate the indexer construct []. Effectively we are then evaluating jdbcProperties['Dave'] because username got evaluated to 'Dave' - and we don't find it because there is no entry in the jdbcProperties properties object for Dave - and so the whole thing returns null.

I think from what I've been playing around with I'll get the 'jdbcProperties[username]' to work and commit that tomorrow.

The only possible confusing thing is then what to do for property names with dots in. If the property name was instead 'user.name', then:

  • this wouldn't be allowed: "jdbcProperties.user.name"
  • this would be "jdbcProperties['user.name']"
  • not sure about this: "jdbcProperties[user.name]" - should we require property names that include dots to always be quoted? Or not?
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 6, 2009

Dave Syer commented

Property names with dots work OK in Groovy / OGNL but only if quoted (so the third example is only going to work if "user" is in the root context with a property "name".

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2009

Andy Clement commented

Thanks Dave.

Ok, I've committed changes to make all these things behave nicely.

For non-dotted property names you can use:

jdbcProperties['username']
jdbcProperties[username]

Without the brackets, a MapAccessor must be registered as a property resolver, then this works:

jdbcProperties.username

In the case of dotted property names, they should be quoted:

jdbcProperties['user.name']

If unquoted then user.name would be resolved as an expression against the root context object and the resultant value used as the key into jdbcProperties.

All this is tested.

So the remaining thing to address here is the case of jdbcProperties.username when used through an ApplicationContext and why it appears the map accessor isn't used correctly. I don't suppose you can give me your snippet of test code that causes the failure Dave - I'm a bit incompetent at writing app context tests!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2009

Dave Syer commented

Thanks Andy. You will find this interesting:

<bean id="andy"
	class="org.springframework.sample.service.ExampleConfigurationTests$Foo">
	<property name="name" value="#{properties.username }" />
</bean>

works (notice the whitespace), but

<bean id="andy"
	class="org.springframework.sample.service.ExampleConfigurationTests$Foo">
	<property name="name" value="#{properties.username}" />
</bean>

doesn't. So the MapAccessor is there but broken? (I don't have your changes yet - still on last night's snapshot.)

Here's the full ApplicationContext:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

	<bean id="dave"
		class="org.springframework.sample.service.ExampleConfigurationTests$Foo">
		<property name="name" value="#{properties['user.name']}" />
	</bean>

	<bean id="andy"
		class="org.springframework.sample.service.ExampleConfigurationTests$Foo">
		<property name="name" value="#{properties.username }" />
	</bean>

	<util:properties id="properties">
		<prop key="user.name">Dave</prop>
		<prop key="username">Andy</prop>
	</util:properties>

</beans>

and the unit test:

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleConfigurationTests {

	public static class Foo {
		private String name;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

	}

	@Autowired
	@Qualifier("dave")
	private Foo dave;

	@Autowired
	@Qualifier("andy")
	private Foo andy;

	@Test
	public void testDave() throws Exception {
		assertEquals("Dave", dave.getName());
	}

	@Test
	public void testAndy() throws Exception {
		assertEquals("Andy", andy.getName());
	}

}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2009

Andy Clement commented

Dave, thanks for giving me all that stuff :) I have the test defined now but it passes just fine even if I change the Andy definition to remove whitespace:

<property name="name" value="#{properties.username}" />

Now I am running with the latest fixes from today which I know you don't have - but I haven't knowingly changed anything that would affect this. The call to trim() in TemplateAwareExpressionParser on line 123 should be trimming the extra whitespace off so that the two variants look identical when passed to the parser. hmmm.

If you wanted to debug a little bit, you could breakpoint in the MapAccessor and see what it is using as the map key? That is org.springframework.context.expression.MapAccessor.canRead() and read(). If (in your case) it is using 'usernam' with the e chopped off, that would be interesting as I might start suspecting some of the string chopping code in the template parser.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2009

Dave Syer commented

Sorry, maybe the snapshots just changed, or maybe I was being an idiot before, but I can't make it fail now. The only test that fails is properties[username] which shows up null (as you predicted), but I'm sure both that and the properties.username used to to be an exception.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 3, 2009

Juergen Hoeller commented

Guys, what's the status on this one? Any further changes or testing required?

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 3, 2009

Andy Clement commented

closing as I believe it is all fixed

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Mar 21, 2011

Dmitry Katsubo commented

Andy, the given syntax does not work in conjunction with ?: operator:

@Value("#{serviceConfigurationProperties.jobStatusPollInterval ?: '10'}")

causes org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 31): Field or property 'jobStatusPollInterval' cannot be found on object of type 'java.util.Properties'

The example was taken from SPR-6806.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jun 16, 2011

Andy Clement commented

The exception occurs because jobStatusPollInterval does not exist on serviceConfigurationProperties. Right now if you wish to look up something that may not exist (as opposed to something that exists but may be null), use "#{serviceConfigurationProperties[jobStatusPollInterval] ?: '10'}".

The current spring mapaccessor will only claim it can read a named property from a map if that map defines the key. If your key isn't there it will say it can't read it and we will fall back to the reflective property resolver which can't find it either (and throws the exception). To make your syntax work we'd have to change the MapAccessor to simply say it can access a property if it is called upon a map, ignoring whether the key exists - this seems a big change.

Are you happy with the alternative syntax I suggested? I'll close this now as some work was done for all Dave's cases, but if you would like your alternative syntax to work, it might be worth a new jira to investigate the impact of changing MapAccessor.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 23, 2012

Dmitry Katsubo commented

I would vote for jdbcProperties['user.name'] to work as jdbcProperties[user.name]. This feature lays apart from original request, so should it be spawned as separate ticket?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 14, 2014

mohan commented

Hi,

This is Mohan, I tried to use Spring EL version 3.2.11.RELEASE. In my application I rid of xml based configuration and using annotations. So, I written Config class to configure beans and loading properties files using annotations. But, I am unable to display the values of property file by giving key using below annotation.
private @Value("#{url}") String jdbcUrl;
This is my config class

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.uprr.app.mcs.spring.dao.DefaultSampleDao;
import com.uprr.app.mcs.spring.dao.SampleDao;
import com.uprr.app.mcs.spring.service.DefaultSampleService;
import com.uprr.app.mcs.spring.service.SampleService;

@Configuration
// @ImportResource("classpath:META-INF/spring/application-context.xml")
@PropertySource("classpath:/META-INF/properties/jdbc.properties")
public class AppConfig {

@Bean
public SampleService sampleService() {
	return new DefaultSampleService();
}


@Bean
public SampleDao sampleRepository() {
	return new DefaultSampleDao();
}

}


@Component
public class DefaultSampleDao implements SampleDao{

private @Value("#{url}") String jdbcUrl;

// private @Value("#{username}") String username;
// private @Value("#{password}") String password;
private @Value("#{systemProperties['os.arch']}") String osArch;
private @Value("#{ T(java.lang.Math).random() * 100.0 }") String mathVal;

public DefaultSampleDao(){}

public void getPropertyDetails(){
	System.out.println("jdbc url : " +jdbcUrl);

// System.out.println("username : " +username);
// System.out.println("password : " +password);
System.out.println("OS Architecture : " +osArch);
System.out.println("OS defaultLocale : " +mathVal);
}

When I tried to execute this from my client application using the below code I am getting the exception

public class SpringTestClient {

public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	Environment env = ctx.getEnvironment();
	
	System.out.println(env.containsProperty("url"));
	System.out.println(env.containsProperty("username"));
	System.out.println(env.containsProperty("mohan"));
    SampleService SampleService = ctx.getBean(SampleService.class);
    SampleService.getPropertyDetails();
	
}

}

exception :

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 48): Property or field 'url' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:226)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:93)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:81)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:126)
at org.springframework.expression.spel.ast.ConstructorReference.createArray(ConstructorReference.java:260)
at org.springframework.expression.spel.ast.ConstructorReference.getValueInternal(ConstructorReference.java:96)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:115)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:240)
at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:161)
... 31 more

Please respond on it.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 14, 2014

Stéphane Nicoll commented

Mohan, please post your question on stackoverflow. Check https://spring.io/questions for more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.