Spring cacheable error, trying to cache a user by ID key and evict it the same way - spring-cache

I am trying to cache using spring's #Cacheable. I'm also using spring mongodb. I keep getting the following error:
java.lang.IllegalArgumentException: Null key returned for cache operation (maybe you are using named params on classes without debug info?) CacheableOperation[public abstract test.models.User test.repositories.UserRepository.findById(java.lang.String)] caches=[userById] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless=''
Here's the code that I'm using. It seems that whether I use "#id" or "id" it doesn't seem to work. Either I get the IllegalArgumentException or it complains about id possibly not being public, but I'd like to keep "id" as private in my User model.
public interface UserRepository extends MongoRepository<User, String> {
#Override
#CacheEvict(value="byId", key="#entity.id")
<S extends User> S save(S entity);
#Cacheable(value="byId", key="#id")
User findById(String id);
public User findByUsername(String username);
}
Could someone please point out what I'm doing wrong? Essentially, I want to cache in redis all "users" underneath users but cache it with the User "id" as the key. Then, I'd like to also evict based on the same id when a user is saved.

FYI, the exception does not match the code (the name of the cache is different).
The key attribute on findById is useless. The cache abstraction is going to use the method arguments by default to compute the key. Since you have only one argument, it will use said argument. You can rewrite the method as follow and get the exact same result:
#Cacheable(value="byId")
User findById(String id);
I can't comment on the other issue. If the cache abstraction complains about something being private, it must be on the cache evict, not on this method. So posting an exception that's consistent with the description will help.

Related

Is that possible to retrieve products without passing catalogType?

I have a requirement to get products by code without knowing catalogType. Is that possible to retrieve products without passing catalogType?
Below is the code snippet I've tried:
#Resource
private ProductDao productDao;
#Resource
private CatalogVersionService catalogVersionService;
List<ProductModel> getProductsByCode(String code) {
CatalogVersionModel catalogVersionModel = new CatalogVersionModel();
catalogVersionModel.setVersion("Online");
catalogVersionService.addSessionCatalogVersion(catalogVersionModel);
List<ProductModel> productModels = productDao.findProductsByCode(code);
}
Below is the exception am getting:
{
"errors": [
{
"message": "model CatalogVersionModel (<unsaved>) cannot be serialized due to being modified, new or removed",
"type": "FlexibleSearchError"
}
]
}
May I know how to fix for above issue?
When you create a product/variant in SAP Commerce (hybris) you must attach it to a catalog.
A catalog (CatalogModel) also have a version (usually staged or online), and the object is called a CatalogVersionModel
When you want to retrieve a product/variant, you must indicate the CatalogVersionModel because the product code is not a unique key to retrieve the product in the DB (you can check the Type "Product" in the backoffice and see in the XML pane that both code and catalogVersion have the value unique="true")
Now in you code there are several issue.
You should not create a catalog version but you should retrive it using a service (See DefaultCatalogVersionService)
You should use a service to retrieve your product (See DefaultProductService)
In productService implementation, you'll find two methods getProductForCode.
One with only the sku code as parameter
One with the sku code and catalogVersion as parameter
The first method actually looks like the method you want, but in fact, it uses the catalogVersion in your session. Your session will be different if you run your code in groovy or if you run your code in Java from your ecommerce website.
You can find the comment of this method below
Returns the Product with the specified code. As default the search uses the current session user, the currentsession language and the current active catalog versions (which are stored at the session in the attribute SESSION_CATALOG_VERSIONS).For modifying the search session context see FlexibleSearchQuery.
You need to specify the catalog, because it is possible to have multiple catalogs, and the same product could exist in all of those catalogs.

GraphQL - How to distinguish Public from Private fields?

Context
I have a GraphQL API and a NodeJS & Angular application with a MongoDB database that holds users. For each user, there is a public page with public information like id and username. When a user is logged in, there is a private profile page with extended information like an email.
Just for context, I'm using jsonwebtoken with accesscontrol to authenticate and authorize a user. The information is stored on the Context of every GraphQL resolve function, so whatever is needed to identify a logged in user is available.
I have a GraphQL query that retrieves a public user like so:
query getUserById($id: ID!) {
getUserById(id: $id) {
id,
username
}
}
I am trying to think of the proper implementation to retrieve either a public or a private user. Since GraphQL is strong typed, I'm having some trouble coming up with a proper solution.
Question
How do I implement the distinction between a public and a private user?
Considerations
1. Separate query
So one of the options is to have a seperate query for both public and private fields:
public query
query getUserById($id: ID!) {
getUserById(id: $id) {
id,
username
}
}
private query
query getMe {
getMe {
id,
username,
email
}
}
2. Using GraphQL Interfaces
I came across this Medium article that explains how GraphQL Interfaces are used to return different Types based on a resolveType function. So I would go something like so:
query getUser($id: ID!) {
getUser(id: $id) {
... on UserPrivate {
id,
username
}
... on UserPublic {
id,
username,
email
}
}
}
I have not came across a proper solution and I'm unsure about either of the consideration I have so far.
Any help is much appreciated!
I think what you are missing here is that in GraphQL you usually want to create this deeply connected graph structure. While getUserByIdand getMe work well as entry points (and I think they are still a great idea even with the interface type), you will most likely have user types coming up all over you schema. Imagine the popular blog post example:
type Post {
id: ID!
title: String!
content: String!
author: User!
}
Adding two author fields here does not really work very well. Similarly in your example you might not know that the profile page is your own until you get a response from the backend (think about twitter profiles).
Instead, in my opinion there are two methods to consider:
First one is the interface idea. You would have an interface that has all the common fields and concrete implementations for the private and public type. The nice thing here: If you only use the common fields you don't even have to use the type matching:
query getUser($id: ID!) {
getUser(id: $id) {
id
username
# if you need a private field you can branch off here
... on UserPrivate {
email
}
}
}
When it gets more finely grained (people share what they want to expose to the public, imagine Facebook) or you have a lot of types (UserMe, UserFriend, UserStranger) you might want to consider nullable fields instead. If you don't have access to the field you will receive null from the API. To reduce the amount of null checking you can easily bundle fields into their own types (e.g. Address).
Summary:
From the API point it is a bit easier to return nullable fields because it gives you a lot of flexibility. It is much easier to evolve the second option without breaking changes than the first one. Using interfaces is more expressive and surely more fun to work with in the frontend if you work with static types (Typescript, Flow, Scala.js, Reason, etc.). Keyword: Pattern matching.

spring-ldap and #attributes annotation with spring-ldap 2.x ODM interface

There seems be some things missing in the Spring-LDAP ODM annotations. This is a question by way of a feature request, if there is a better way to contribute such requests, please say so.
I'd like to mark an #Attribute as read-only, so it will populate the bean from LDAP for reference, but not persist it back to ldap. I'd suggest adding an attribute read-only to #Attribute, defaulting to false, for the usual case. The default attributes of * misses all the operational attributes, some of which are very useful, and transfers more data than is required, slowing down the ldap query with attributes which will never be used.
An example of this; it would be very useful, for literally read only, such as entryUUID, etag, etc., which you cannot use if you wish to persist only some fields back to ldap, as the bean fails to persist to ldap with an exception when you save the bean. But also would be usefule for general fields which you want to structurally prevent the user from ever updating.
You can get around this by not annotating read-only fields, and then manually populating the read only fields with a separate call. Very messy and kills the query speed.
Also on a related topic, query() coudl have a default list of attributes, which you have already annotated in your classes, something like :
public static String[] getBeanAttributes(Class<?> beanClass) {
ArrayList<String> attrsObj = new ArrayList<>();
for (Field field : beanClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Attribute.class)) {
Attribute attr = field.getAnnotation(Attribute.class);
attrsObj.add(attr.name());
}
}
String[] attrs = attrsObj.toArray(new String[attrsObj.size()]);
return attrs;
}
Above just returns a simple String[] of your declared attributes, to pass to query.attributes() - now i realize that as a static member, query() is built before the bean class is known, but at least there could be a helper function like the above, or a method signature for query attributes() that took a bean Class signature as an argument.
I created LDAP-312 on Jira. Thanks.

Jax-rs QueryParam causing multi-thread issue when defined as the resource POJO member field

Question Description :
I have a JAX-RS resource pojo defined as below (outside is cxf container inregrated with spring, running in a tomcat)
#Path("/test/{id}")
#Consumes(MediaType.APPLICATION_JSON)
public class TestService {
#PathParam("id") private String id;
#GET
public Response get() throws Exception{
return Response.ok().entity(id).build();
}
}
Then I use jmeter to send some load with auto-increasing "id" parameters to the server. And I got this issue : the id in the response doesn't match that was sent.
E.g. request "localhost:8090/test/100" will get a "87" in the response.
And The frequency of error increases by using more client threads or making the handler method slower like this :
#GET
public Response get() throws Exception{
return Response.ok().entity(id).build();
Thread.sleep(500);
}
My thinking and confusion: The TestService is used as a singleton and since the "id" is a shared
field, so it MAY cause inconsistency issue when there are multiple threads running the "get()" function because it uses the shared "id". And then I put the "id" into the method parameter issue was resolved :
#Path("/test/{id}")
#Consumes(MediaType.APPLICATION_JSON)
public class TestService {
#GET
public Response get(#PathParam("id") String id) throws Exception{
return Response.ok().entity(id).build();
}
}
My confusion is : If this is a existing problem, I did saw lots of places and articles with the first style of using #PathParam, even in the jsr-339-jaxrs final spec?
![code snippets from jsr-339-jaxrs final spec][1]
Or both style there is good but I made some mistakes on my code?
Thanks!
A quick look at the docs seems to suggest that in CXF, with Spring, resources are treated as singletons by default:
"By default, the service beans which are referenced directly from the jaxrs:server endpoint declarations are treated by the runtime as singleton JAX-RS root resources"
Apache CXF Docs - Lifecycle Management Section
But in Jersey, the JAX-RS reference implementation, root resources are treated as dependent scoped (a new one is created on each request) unless otherwise specified.
By default the life-cycle of root resource classes is per-request which, namely that a new instance of a root resource class is created every time the request URI path matches the root resource.
See section 3.4 in https://jersey.java.net/documentation/latest/jaxrs-resources.html
So, if you are using CXF with Spring, your resources are likely singletons unless you configure them to be Spring Prototypes. With dependent scoped injection, #PathParam as an instance field should be fine, but in a singleton scope it you would expect to see issues like you describe.

App Fabric : While GET misses an Enum property

I have a class marked as CollectionDataContract which has a enum member. When I place an object of this class in Appfabric, I am through. When I get it back from App fabric, it does not deserialize the enum member. But I am not sure if the enum has been missed out in Serialization part itself.
Please do help.
If you need more information let me know.
Thanks.
[CollectionDataContract]
public partial class RuleConditionList : List<IRuleCondition>, IRuleCondition
{
public LogicalOperator Operator;
}
where LogicalOperator is an enum
I think there is a problem when serializing/deserializing your object. AppFabric uses the NetDataContractSerializer class for serialization before storing the items in the cache.
You can use the Net­Dat­a­Con­tract­Se­ri­al­izer on any type which are marked with the Dat­a­Con­trac­tAt­tribute or Seri­al­iz­ableAt­tribute, or types that imple­ment the ISe­ri­al­iz­able interface.
So depending and your object, there should be something wrong like a private type, a private field, a missing attibute, ...
Edit
You should add DataMember to your field.
[DataMember]
public LogicalOperator Operator;
Any data member in a class marked with Collection data contract cannot be serialized by NetdataContractSerializer which is the serailization technique used by App fabric for storing data.
To make things work we have two options:
Make a wrapper for RuleConditionList
Instead of Inheriting from List, make it as a property and change the attribute as DataContract.

Resources