spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.66k stars 38.14k forks source link

Add Support for Java 8 Optional to the Spring Expression Language [SPR-15878] #20433

Closed spring-projects-issues closed 10 months ago

spring-projects-issues commented 7 years ago

Peter Luttrell opened SPR-15878 and commented

This is a feature request to add support for Java 8 Optionals to the Spring Expression Language.

One use case that I just ran into is wanting to use @PostAuthorize on a method that returns an Optional in conjunctions with custom expressions. For example the following fails:

@PostAuthorize("canAccessOrganization(returnObject.organiztionId)")
public Optional<Person> getPerson(String personId){
    ...
}

In this case, if the returned reference isn't present, that @PostAuthorize would allow the response, which should be Optional.empty(). If it is present, then it'd be dereferenced into the returnObject, so we'd have direct access to its fields for use in the expression.


4 votes, 6 watchers

spring-projects-issues commented 7 years ago

Juergen Hoeller commented

Rob Winch, I figure this might have to be handled in Spring Security's authorization interceptor, specifically detecting an Optional return value there and unwrapping it?

spring-projects-issues commented 7 years ago

Rob Winch commented

Thanks for the report Peter Luttrell

. For example the following fails:

This does fail because Optional does not have a method of getOrganizationId() on it.

In this case, if the returned reference isn't present, that @PostAuthorize would allow the response,

This is not true. In either case, the PostAuthorize will fail because Optional does not have a method getOrganizationId()

Juergen Hoeller I don't think it really makes sense to automatically unwrap Optional. If the method signature is Optional, then returnType should be Optional. If Optional is automatically unwrapped, then how would someone use Optional return types?

Users can always do something like @PostAuthorize("canAccessOrganization(returnObject.orElse(null)?.organiztionId)"). Finally, if someone really wants to automatically unwrap the returnValue when it is Optional, they can override DefaultMethodSecurityExpressionHandler.setReturnObject.

spring-projects-issues commented 6 years ago

Mohamed Amine Mrad commented

Hello, I was not able to create an issue. I have a suggestion to add here: In fact SpEL should support writing an Optional properly like writing an Enum. There is an EnumToStringConverter added to DefaultConversionService method addScalarConverters There should be an OptionalToStringConverter also. I'm using thymeleaf and I'm struggling with the ugly get() in HTML.

spring-projects-issues commented 6 years ago

Stéphane Toussaint commented

I have another use case for this feature request.

I use Spring Integration and some of my service layer bean methods are now returning Optional generic types.

This is a sample of two successive service-activator call, the payload is the Optional\ return from retrieveByUsername.

<int:service-activator expression="@personService.retrieveByUsername(headers.username)" />
<int:service-activator expression="@personService.doWithPerson(payload)" />

The doWithPerson method has not changed ; still waiting for a Person.

Class PersonService {
  public void doWithPerson(Person person) {
    ...
  }
}

The retrieveByUsername however now returns an Optional of Person

Class PersonService {
  public Optional<Person> retrieveByUsername(String username) {
    return Optional.of(...)
  }
}

Now Spring Integration (actually the Spring Expression Evaluator) complains it can't find the targeted method with a message like :

Expression evaluation failed: @personService.doWithPerson(payload); nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method doWithPerson(java.util.Optional) cannot be found on PersonService.

Will an OptionalToObjectConverter be feasible ? Maybe it can be possible to rely on Nullable annotation on the target method to handle the Optional.empty() case ?(return null or throw ?).

wimdeblauwe commented 4 years ago

Another use case is with @PreAuthorize. Assume a Task has a reference to a User and there is a TaskServiceImpl class with:

    Optional<Task> getTask(int taskId);

The controller could check if the task is linked to the authorized user like this:

    @DeleteMapping("/{taskId}")
    @PreAuthorize("@taskServiceImpl.getTask(#taskId).orElse(null)?.user.id == #userDetails.id")
    public String destroy(@AuthenticationPrincipal ApplicationUserDetails userDetails,
                          @PathVariable("taskId") Integer taskId) {
        return "redirect:/tasks";
    }

The orElse(null) is perfect for this, not sure if anything else is needed, but just wanted to let you know about this use case.

sanatik commented 4 years ago

My use case would be with @Cacheable

@Cacheable(unless = "#result.isEmpty()")
public Optional<User> getUserById(final String userId);
dkfellows commented 3 years ago

At the very least, @PreFilter and @PostFilter ought to work with Optional as if they were collections containing at most one object. If the filter sees a non-empty optional but doesn't want to let the contained object through, that becomes an empty optional; the obvious semantics.

OrangeDog commented 3 years ago

This also applies to Thymeleaf templating, which uses SpEL in the default Boot configuration.

cheatmenot commented 2 years ago

+1