Spring expression language: Using a variable as key for a map lookup in SpEL - spring

I'm trying to use variable as key to look up a value in a map. I would like to be able to reference the variable directly (in this case jobTitle), but for some reason I need to prefix the variable with either #root. or #this. in order to get it to work. So this works:
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[#root.jobTitle]"
(resolves to "37.5")
but this doesn't
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[jobTitle]"
(resolves to null)
jobTitle is a root attribute on the context object. From looking at the SpEL docs it seems like I should be able to reference the attribute directly? Am I doing something wrong?
Full working code below
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Employee employee = new Employee("Joe Bloggs", "Manager");
Expression exp = parser.parseExpression(
"{ \"Manager\":\"37.5\", \"Ceo\":\"40\"}[#root.jobTitle]");
StandardEvaluationContext context = new StandardEvaluationContext(employee);
context.setRootObject(employee);
System.out.println(exp.getValue(context, String.class));
}
static class Employee {
private String firstName;
private String jobTitle;
Employee(String firstName, String jobTitle) {
this.firstName = firstName;
this.jobTitle = jobTitle;
}
public String getJobTitle() {
return jobTitle;
}
}

From looking at the SpEL docs it seems like I should be able to reference the attribute directly?
This is correct for below case, both will print "Manager"
System.out.println(parser.parseExpression("#root.jobTitle").getValue(context, String.class));
System.out.println(parser.parseExpression("jobTitle").getValue(context, String.class));
But for expression inside [], the handling is different. Since there is no language specification for SpEL, I can just explain base on the behaviour.
When you run
System.out.println(parser.parseExpression(
"{\"jobTitle\":\"37.5\"}[jobTitle]").getValue(context, String.class));
This will actually print "37.5"([jobTitle] is treated as [\"jobTitle\"]). The author decided to make it easier to access value of Map, and the drawback is you need to specify with #root.jobTitle when referencing the root object.

Related

How can I test a session method?

I'm trying to #Test a Service class but there are several get() methods that I don't know how to test. I would need to know how to collect the data that is necessary or at least how to test the rest of the methods of the TokenHelper class.
This is the Session class:
public class SessionData {
public static final String KEY = "session_data";
private Integer id;
private String email;
private String fullName;
private List<Role> role;
private Boolean tempSession;
private int permissionsMask = 0;
private String avatar;
public boolean hasAnyRole(Role... roles) {
for (Role r : roles) {
if (this.role.contains(r)) {
return true;
}
}
return false;
}
}
This is the TokenHelper class:
public class TokenHelper {
public String generate(SessionData tokenData, long expirationInHours) {
return Jwts.builder()
.claim(SessionData.KEY, tokenData)
.setIssuedAt(Date.from(Instant.now()))
.setExpiration(Date.from(Instant.now().plus(expirationInHours, ChronoUnit.HOURS)))
.signWith(SignatureAlgorithm.HS256, TextCodec.BASE64.encode(secret))
.compact();
}
public UserGoogle getTokenDataFromGoogleToken(String token) throws InvalidTokenException {
try {
int i = token.lastIndexOf('.');
String withoutSignature = token.substring(0, i + 1);
Claims claims = Jwts.parser().parseClaimsJwt(withoutSignature).getBody();
return UserGoogle.builder()
.email(claims.get(UserGoogle.KEY_EMAIL).toString())
.firstName(claims.get(UserGoogle.KEY_FIST_NAME).toString())
.lastName(claims.get(UserGoogle.KEY_LAST_NAME).toString()).build();
} catch (ExpiredJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) {
log.error(ERROR_TOKEN, ex.toString());
throw new InvalidTokenException();
}
}
}
This is my #Test:
#Test
void googleTokenHelperTest() throws InvalidTokenException {
TokenHelper obj1 = BeanBuilder.builder(TokenHelper.class).createRandomBean();
String mailGoogle = "google#prueba.com";
String firstGoogle = "Nombre";
String lastGoogle = "Apellido";
Map<String, Object> pruebaGoogle = new HashMap<String, Object>();
List<String> info = new ArrayList<String>();
info.add(firstGoogle);
info.add(lastGoogle);
pruebaGoogle.put(mailGoogle, info);
UserGoogle expectedUser = UserGoogle.builder().email(mailGoogle).firstName(firstGoogle).lastName(lastGoogle).build();
String myTestToken = pruebaGoogle.toString();
UserGoogle actualUser = obj1.getTokenDataFromGoogleToken(myTestToken);
assertEquals(actualUser, expectedUser);
}
I have created some variables to form a user, but I need to build them with a map to generate the token with the help of the generate () method. I need to know how to join those three variables and pass them to the generate () method, and then pass the result variable to the google method to generate the new user.
Edit: After clarification by OP the topic of the question changed.
Your problem arises from a flawed Object-Orientation-Design. For example, your SessionData implicitly holds a User by having String-fields relevant to a User among fields relevant to a Session. This overlapping makes it hard to test your code, because in order to test your Token-Generation for some User data, you need a Session object, which introduces additional data and dependencies.
That is one major reason, why it's difficult for you, to get a token from your three input values.
You want to test getTokenDataFromGoogleToken(String token). First thing you need to know is, what a valid Token-String will look like.
Next, you will need to mock your Claims claims object in one of two ways:
Mockito.mock it using Mockito to return the necessary Strings when claims.get() is called.
Mockito.mock your Jwts.parser().parseClaimsJwt(withoutSignature).getBody() to return a Claims object that serves your testing purpose.
Since the signature of your token will be irrelevant to your tested method, just focus on the substring before the .-Separator, i.e. the part after . in your token string can be any string you like.
If you want to test generate(SessionData, long) you need to supply a SessionData Object and a long value. After that you assertEquals the String as necessary. However, currently your code does not imply that your get is in any way related to your generate. This is, because you just handle Strings. A better design would be to have e.g. a User, Session and Token-classes, which would also make it easier to test your application and units.
A Test for your getToken method looks like the following, you just have to replace ... with your test data.
#Test
void givenGoogleToken_whenTokenHelperGeneratesUserFromToken_UserOk() {
TokenHelper helper = new TokenHelper();
String myTestToken = ...; //
UserGoogle expectedUser = ... // generate the UserGoogle Object you expect to obtain from your TokenHelper class
UserGoogle actualUser = helper.getTokenDataFromGoogleToken(myTestToken);
assertEquals(actualUser, expectedUser);
}
Test generally follow a given-when-then structure. Given some precondition, when some action is performed, then some result is returned/behaviour observed. When implemented very formally, this is called BDD (Behaviour Driven Development), but even when not practicing BDD, tests still generally follow that pattern.
In this case, I would suggest the tests be something like:
Given some data exists in the service threaddata
when I call get
then I get back the expected value
In the scenario above, the given part probably consists of setting some data on the service, the when is invoking get and the then is asserting that it's the expected value.
And I'd encourage you to consider the various scenarios. E.g what happens if the data isn't there? what happens if it's not the class the consumer asks for? Is the map case-sensitive? etc...
Code sample for the initial instance (I'm not sure what BeanBuilder is here, so I've omitted it):
#Test
public void testCurrentThreadServiceReturnsExpectedValue() {
final String key = "TEST KEY";
final String value = "TEST VALUE";
//Initialize System Under Test
CurrentThreadService sut = new CurrentThreadService();
//Given - precondition
sut.set(key, value);
//When - retrieve value
String observedValue = sut.get(key, String.class);
//Then - value is as expected
assertEquals(value, observedValue);
}
EDIT TO ADD It's always great to see someone get into unit testing, so if you have any follow ups, please ask I'm happy to help. The confidence one derives from well tested code is a great thing for software devs.

GraphQl Java, How can I blindly return all variables associated with an object from query and question on handling sub classes

I'm new to GraphQL and I'm currently implementing a GraphQL API into an established Java code, using GraphQL-SPQR and I'm running into a couple issues when it comes extracting data from hierarchical classes.
The issues that I am running into are as follows.
Firstly I don't if there is an easy way to get all the data associated with a returned node. If there is, this would be most useful for my more complex classes.
Secondly when a method returns an abstract class, I only seem able to request the variables on the abstract class. I'm sure this should be possible I am just hitting my head against a wall.
As a simple example
public abstract class Animal {
private String name;
private int age;
// Constructor
#GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
public class Dog extends Animal {
private String favouriteFood;
// Constructor
#GraphQLQuery(name = "favouriteFood")
public String getFavouriteFood() {
return favouriteFood;
}
}
public class Database {
#GraphQLQuery(name = "getanimal")
public Animal getAnimal(#GraphQLArgument(name = "animalname") String animalname) {
return database.get(name);
}
}
So in my first question what I am currently querying is.
"{animalname(name: \"Daisy\") {name age}}"
This works fine as expected. If you imagine the class however had 10 variables I would like to merely be able to write the equivalent of the following without having to look them up.
"{node(name: \"Daisy\") {ALL}}"
Is this possible?
In terms of my second question.
The follow query, throws an error ('Field 'favouriteFood' in type 'Animal' is undefined')
"{animalname(name: \"Bones\") {name age favouriteFood}}"
likewise (reading Inline Fragments of https://graphql.org/learn/queries/)
"{animalname(name: \"Bones\") {name age ... on Dog{favouriteFood}}}"
throws an error Unknown type Dog
This is annoying as I have a number of sub classes which could be returned and may require handling in different fashions. I think I can understand why this is occuring as GraphQL has no knowledge as to what the true class is, only the super class I have returned. However I'm wondering if there is a way to fix this.
Ultimately while I can get past both these issues by simply serialising all the data to JSON and sending it back, it kind of gets rid of the point of GraphQL and I would rather find an alternate solution.
Thank you for any response.
Apologies if these are basic questions.
Answering my own question to help anyone else who has this issue.
The abstract class needs to have #GraphQLInterface included, as shown below
#GraphQLInterface(name = "Animal ", implementationAutoDiscovery = true)
public abstract class Animal {
private String name;
private int age;
// Constructor
#GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
The following code was found after much solution and was created by the creator of SPQR. Effectively, when setting up your schema you need to declare an interface mapping strategy. The code below can be copied wholesale with only the "nodeQuery" variable being replaced with the service you are using to containing your "#GraphQLQuery" and "#GraphQLMutation" methods.
final GraphQLSchema schema = new GraphQLSchemaGenerator()
.withInterfaceMappingStrategy(new InterfaceMappingStrategy() {
#Override
public boolean supports(final AnnotatedType interfase) {
return interfase.isAnnotationPresent(GraphQLInterface.class);
}
#Override
public Collection<AnnotatedType> getInterfaces(final AnnotatedType type) {
Class clazz = ClassUtils.getRawType(type.getType());
final Set<AnnotatedType> interfaces = new HashSet<>();
do {
final AnnotatedType currentType = GenericTypeReflector.getExactSuperType(type, clazz);
if (supports(currentType)) {
interfaces.add(currentType);
}
Arrays.stream(clazz.getInterfaces())
.map(inter -> GenericTypeReflector.getExactSuperType(type, inter))
.filter(this::supports).forEach(interfaces::add);
} while ((clazz = clazz.getSuperclass()) != Object.class && clazz != null);
return interfaces;
}
}).withOperationsFromSingleton(nodeQuery)// register the service
.generate(); // done ;)
graphQL = new GraphQL.Builder(schema).build();
As this solution took some hunting, I'm going to start a blog soon with the other solutions I've stumbled on.
With regards to having a query that just returns all results. This is not possible in GraphQL. One workaround I might write is to have a endpoint that returns JSON of the entire object and the name of the object, then I can just use ObjectMapper to convert it back.
I hope this helps other people. I'm still looking into an answer for my first question and will update this post when I find one.

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

Long Polling with Spring's DeferredResult

The client periodically calls an async method (long polling), passing it a value of a stock symbol, which the server uses to query the database and return the object back to the client.
I am using Spring's DeferredResult class, however I'm not familiar with how it works. Notice how I am using the symbol property (sent from client) to query the database for new data (see below).
Perhaps there is a better approach for long polling with Spring?
How do I pass the symbol property from the method deferredResult() to processQueues()?
private final Queue<DeferredResult<String>> responseBodyQueue = new ConcurrentLinkedQueue<>();
#RequestMapping("/poll/{symbol}")
public #ResponseBody DeferredResult<String> deferredResult(#PathVariable("symbol") String symbol) {
DeferredResult<String> result = new DeferredResult<String>();
this.responseBodyQueue.add(result);
return result;
}
#Scheduled(fixedRate=2000)
public void processQueues() {
for (DeferredResult<String> result : this.responseBodyQueue) {
Quote quote = jpaStockQuoteRepository.findStock(symbol);
result.setResult(quote);
this.responseBodyQueue.remove(result);
}
}
DeferredResult in Spring 4.1.7:
Subclasses can extend this class to easily associate additional data or behavior with the DeferredResult. For example, one might want to associate the user used to create the DeferredResult by extending the class and adding an additional property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
You can extend DeferredResult and save the symbol parameter as a class field.
static class DeferredQuote extends DeferredResult<Quote> {
private final String symbol;
public DeferredQuote(String symbol) {
this.symbol = symbol;
}
}
#RequestMapping("/poll/{symbol}")
public #ResponseBody DeferredQuote deferredResult(#PathVariable("symbol") String symbol) {
DeferredQuote result = new DeferredQuote(symbol);
responseBodyQueue.add(result);
return result;
}
#Scheduled(fixedRate = 2000)
public void processQueues() {
for (DeferredQuote result : responseBodyQueue) {
Quote quote = jpaStockQuoteRepository.findStock(result.symbol);
result.setResult(quote);
responseBodyQueue.remove(result);
}
}

Spring MVC: how to indicate whether a path variable is required or not?

I am doing a Spring web. For a controller method, I am able to use RequestParam to indicate whether a parameter it is required or not. For example:
#RequestMapping({"customer"})
public String surveys(HttpServletRequest request,
#RequestParam(value="id", required = false) Long id,
Map<String, Object> map)
I would like to use PathVariable such as the following:
#RequestMapping({"customer/{id}"})
public String surveys(HttpServletRequest request,
#PathVariable("id") Long id,
Map<String, Object> map)
How can I indicate whether a path variable is required or not? I need to make it optional because when creating a new object, there is no associated ID available until it is saved.
Thanks for help!
VTTom`s solution is right, just change "value" variable to array and list all url possibilities: value={"/", "/{id}"}
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
There's no way to make it optional, but you can create two methods with one having the #RequestMapping({"customer"}) annotation and the other having #RequestMapping({"customer/{id}"}) and then act accordingly in each.
I know this is an old question, but searching for "optional path variable" puts this answer high so i thought it would be worth pointing out that since Spring 4.1 using Java 1.8 this is possible using the java.util.Optional class.
an example would be (note the value must list all the potential routes that needs to match, ie. with the id path variable and without. Props to #martin-cmarko for pointing that out)
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
VTToms answer will not work as without id in path it will not be matched (i.e will not find corresponding HandlerMapping) and consequently controller will not be hit. Rather you can do -
#RequestMapping({"customer/{id}","customer"})
public String surveys(HttpServletRequest request, #PathVariable Map<String, String> pathVariablesMap, Map<String, Object> map) {
if (pathVariablesMap.containsKey("id")) {
//corresponds to path "customer/{id}"
}
else {
//corresponds to path "customer"
}
}
You can also use java.util.Optional which others have mentioned but it requires requires Spring 4.1+ and Java 1.8..
There is a problem with using 'Optional'(#PathVariable Optional id) or Map (#PathVariable Map pathVariables) in that if you then try to create a HATEOAS link by calling the controller method it will fail because Spring-hateoas seems to be pre java8 and has no support for 'Optional'. It also fails to call any method with #PathVariable Map annotation.
Here is an example that demonstrates the failure of Map
#RequestMapping(value={"/subs","/masterclient/{masterclient}/subs"}, method = RequestMethod.GET)
public List<Jobs> getJobListTest(
#PathVariable Map<String, String> pathVariables,
#RequestParam(value="count", required = false, defaultValue = defaultCount) int count)
{
if (pathVariables.containsKey("masterclient"))
{
System.out.println("Master Client = " + pathVariables.get("masterclient"));
}
else
{
System.out.println("No Master Client");
}
//Add a Link to the self here.
List list = new ArrayList<Jobs>();
list.add(linkTo(methodOn(ControllerJobs.class).getJobListTest(pathVariables, count)).withSelfRel());
return list;
}
I know this is an old question, but as none of the answers provide some updated information and as I was passing by this, I would like to add my contribution:
Since Spring MVC 4.3.3 introduced Web Improvements,
#PathVariable(required = false) //true is default value
is legal and possible.
#RequestMapping(path = {"/customer", "/customer/{id}"})
public String getCustomerById(#PathVariable("id") Optional<Long> id)
throws RecordNotFoundException
{
if(id.isPresent()) {
//get specific customer
} else {
//get all customer or any thing you want
}
}
Now all URLs are mapped and will work.
/customer/123
/customer/1000
/customer - WORKS NOW !!

Resources