Get all attributes from Active Directory using Spring LdapTemplate - spring

I have a Spring Boot application that uses LDAP to authenticate the users. For the users, I am mapping the attributes from AD and populating the values like the user's first name, last name, department, email, telephone, and also the image. However, I am unable to get the employee number from the attributes.
When I check the attributes using the tool Active Directory explorer, I am able to see 88 attributes per entry. However, when I print every attribute from the context using this code,
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
String email = ctx.getStringAttribute("mail");
String department = ctx.getStringAttribute("department");
String empNumber = ctx.getStringAttribute("employeeNumber");
System.out.println(empNumber); // this prints null
System.out.println(ctx.attributeExists("employeeNumber")); // this prints false
byte[] value= (byte[])ctx.getObjectAttribute("thumbNailPhoto");
BASE64Encoder base64Encoder = new BASE64Encoder();
StringBuilder imageString = new StringBuilder();
imageString.append("data:image/jpg;base64,");
imageString.append(base64Encoder.encode(value));
String image = imageString.toString();
Attributes attributes = ctx.getAttributes();
NamingEnumeration<? extends Attribute> namingEnumeration = attributes.getAll();
try {
while(namingEnumeration.hasMore()){
/*this loop prints 75 attributes but employeeNumber attribute is missing along with some other attributes*/
Attribute attribute = namingEnumeration.next();
System.out.println(attribute);
}
} catch (NamingException e) {
e.printStackTrace();
}
CustomUserDetails userDetails = (CustomUserDetails)userService.loadUserByUsername(username);
userDetails.setImage(image);
userDetails.setEmail(email);
userDetails.setDepartment(department);
return userDetails;
}
};
}
only 75 attributes are printed. Why is it that some of the attributes are not retrieved? how can i access those attributes?

I think you need to expand array elements like memberof.
Try this.. it may help.
Attribute attribute = namingEnumeration.next();
System.out.println(attribute);
System.out.println(attribute.size());
if size is greater than one.. expand it again

Related

Unable to get modification context for Active Directory records through Spring LDAP

I am trying to use Spring LDAP to retrieve and modify user information in an Active Directory server, but I can't retrieve a user record by dn so that I can modify it.
I am able to find the record by username with the LdapTemplate.search method. There is no dn attribute in the record, but distinguishedName looks like it should be correct. When I use LdapTemplate.lookupContext to retrieve the record by dn, however, the server says that it can't find the record by the dn that it just gave me. What am I doing wrong?
It seem wrong that the LdapTemplate search method doesn't give you a handle that you can use without doing a second query from the Active Directory. Is there a better way to do this?
I have created a sample Groovy application to demonstrate the problem. My Spring Boot application creates this class and then invokes the runTest method.
package edu.sunyjcc.gateway.ldap;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import org.springframework.ldap.core.DirContextOperations;
public class ActiveDirectoryDNSample {
LdapTemplate ldapTemplate;
/** Attributes to fetch from server */
static attributeList = [
"sAMAccountName",
"distinguishedName",
"baseDn",
"userPrincipalName",
];
/** This will represent the record retrieved from Active Directory */
class Person {
/** Raw data from server */
Map attributes = [:];
/** Return the distinguished name */
String getDn() {
attributes?.distinguishedName;
}
String getUsername() {
attributes?.sAMAccountName;
}
String toString() {
"${this.username} <${this.getDn()}>"
}
/** Get a handle to the object from AD so we can modify it. This fails. */
def getContext() {
assert ldapTemplate;
println "in getContext()";
def dn = new LdapName(this.getDn());
println "...dn=$dn"
assert dn;
// The next line throws an exception.
DirContextOperations context = ldapTemplate.lookupContext(dn);
println "...context=$context"
}
}
/** Convert the attributes from AD into a Person object */
class RecordMapper implements AttributesMapper<Person> {
/** Create a Person object from the attribute map */
Person mapFromAttributes(Attributes attributes)
throws NamingException {
assert ldapTemplate;
Person prec = new Person(
ldapTemplate: ldapTemplate
);
attributeList.collect {
[attrName: it, attr: attributes.get(it)]
}.grep {it.attr}.each {
prec.attributes."${it.attrName}" = it.attr.get() as String;
}
return prec;
}
}
/** Get a user from Active Directory */
public List<Person> getByUsername(String username) throws Exception {
assert ldapTemplate;
AttributesMapper attrMapper = new RecordMapper();
assert attrMapper;
List s = ldapTemplate.search(
query().
where("sAMAccountName").is(username),
attrMapper
);
if (s == null) {
System.err.println("s is null");
}
return s?:[];
}
/** Try to fetch a record and get a modify context for it */
public runTest(String username) {
println "In ActiveDirectoryDNSample.runText($username)"
assert ldapTemplate;
def records = getByUsername(username);
println "Retrieved ${records?.size()} records";
records.each {println " $it"}
println "Now try to get the context for the records"
records.each {
person ->
println " getting context for $person";
def context = person.getContext();
println " context=$context"
}
}
public ActiveDirectoryDNSample(LdapTemplate ldapTemplate ) {
this.ldapTemplate = ldapTemplate;
}
}
In ActiveDirectoryDNSample.runText(testuser)
Retrieved 1 records
testuser <CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu>
Now try to get the context for the records
getting context for testuser <CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu>
in getContext()
...dn=CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu
and then it dies with a javax.naming.NameNotFoundException with the following data.
[LDAP: error code 32 - 0000208D: NameErr: DSID-03100238, problem 2001 (NO_OBJECT), data 0, best match of:
'CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu'
\0]
Thanks for any help you can give me.
It turns out that there was, indeed, a better way. Instead of using an org.springframework.ldap.core.AttributesMapper in the search, you use org.springframework.ldap.core.ContextMapper.
In my example, I added a field to the Person class, which will hold a reference to the context.
DirContextOperations context;
Then I created a new class extending org.springframework.ldap.core.support.AbstractContextMapper.
class PersonContextMapper extends AbstractContextMapper {
#Override
protected Object doMapFromContext(DirContextOperations ctx) {
AttributesMapper attrMapper = new RecordMapper();
Person p = attrMapper.mapFromAttributes(ctx.attributes);
p.context = ctx;
return p;
}
}
When I passed it to the ldapTemplate.search method in the place of the AttributeMapper, I was able to use the context to update the Active Directory.

Gson: How do I deserialize an inner JSON object to a map if the property name is not fixed?

My client retrieves JSON content as below:
{
"table": "tablename",
"update": 1495104575669,
"rows": [
{"column5": 11, "column6": "yyy"},
{"column3": 22, "column4": "zzz"}
]
}
In rows array content, the key is not fixed. I want to retrieve the key and value and save into a Map using Gson 2.8.x.
How can I configure Gson to simply use to deserialize?
Here is my idea:
public class Dataset {
private String table;
private long update;
private List<Rows>> lists; <-- little confused here.
or private List<HashMap<String,Object> lists
Setter/Getter
}
public class Rows {
private HashMap<String, Object> map;
....
}
Dataset k = gson.fromJson(jsonStr, Dataset.class);
log.info(k.getRows().size()); <-- I got two null object
Thanks.
Gson does not support such a thing out of box. It would be nice, if you can make the property name fixed. If not, then you can have a few options that probably would help you.
Just rename the Dataset.lists field to Dataset.rows, if the property name is fixed, rows.
If the possible name set is known in advance, suggest Gson to pick alternative names using the #SerializedName.
If the possible name set is really unknown and may change in the future, you might want to try to make it fully dynamic using a custom TypeAdapter (streaming mode; requires less memory, but harder to use) or a custom JsonDeserializer (object mode; requires more memory to store intermediate tree views, but it's easy to use) registered with GsonBuilder.
For option #2, you can simply add the names of name alternatives:
#SerializedName(value = "lists", alternate = "rows")
final List<Map<String, Object>> lists;
For option #3, bind a downstream List<Map<String, Object>> type adapter trying to detect the name dynamically. Note that I omit the Rows class deserialization strategy for simplicity (and I believe you might want to remove the Rows class in favor of simple Map<String, Object> (another note: use Map, try not to specify collection implementations -- hash maps are unordered, but telling Gson you're going to deal with Map would let it to pick an ordered map like LinkedTreeMap (Gson internals) or LinkedHashMap that might be important for datasets)).
// Type tokens are immutable and can be declared constants
private static final TypeToken<String> stringTypeToken = new TypeToken<String>() {
};
private static final TypeToken<Long> longTypeToken = new TypeToken<Long>() {
};
private static final TypeToken<List<Map<String, Object>>> stringToObjectMapListTypeToken = new TypeToken<List<Map<String, Object>>>() {
};
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getRawType() != Dataset.class ) {
return null;
}
// If the actual type token represents the Dataset class, then pick the bunch of downstream type adapters
final TypeAdapter<String> stringTypeAdapter = gson.getDelegateAdapter(this, stringTypeToken);
final TypeAdapter<Long> primitiveLongTypeAdapter = gson.getDelegateAdapter(this, longTypeToken);
final TypeAdapter<List<Map<String, Object>>> stringToObjectMapListTypeAdapter = stringToObjectMapListTypeToken);
// And compose the bunch into a single dataset type adapter
final TypeAdapter<Dataset> datasetTypeAdapter = new TypeAdapter<Dataset>() {
#Override
public void write(final JsonWriter out, final Dataset dataset) {
// Omitted for brevity
throw new UnsupportedOperationException();
}
#Override
public Dataset read(final JsonReader in)
throws IOException {
in.beginObject();
String table = null;
long update = 0;
List<Map<String, Object>> lists = null;
while ( in.hasNext() ) {
final String name = in.nextName();
switch ( name ) {
case "table":
table = stringTypeAdapter.read(in);
break;
case "update":
update = primitiveLongTypeAdapter.read(in);
break;
default:
lists = stringToObjectMapListTypeAdapter.read(in);
break;
}
}
in.endObject();
return new Dataset(table, update, lists);
}
}.nullSafe(); // Making the type adapter null-safe
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) datasetTypeAdapter;
return typeAdapter;
}
})
.create();
final Dataset dataset = gson.fromJson(jsonReader, Dataset.class);
System.out.println(dataset.lists);
The code above would print then:
[{column5=11.0, column6=yyy}, {column3=22.0, column4=zzz}]

In Hibernate using Lazy loading instead of eager loading

I am working on a Spring-MVC application in which the user can register products and productImages. Now there are 3 tables, User, Product, ProductImages. It is not always necessary to pull all the productImages until and unless the user explicitly goes into the product, where there is a modal, and the user can then select the images to load.
So I thought of using LazyLoading instead of EagerFetching. But I get lazyLoadException with that. So I opened a session manually in both Product and ProductImages, and I get a ObjectNotFound Exception. The problem is, productImages has a foreign key relation with product, so I must save product first before saving its images, and that is where I am having the problem. Kindly suggest me how to use lazy load in this situation. Error log and code goes below :
Error log :
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.WirTauschen.model.ProductImage#1150]
org.hibernate.internal.SessionFactoryImpl$1$1.handleEntityNotFound(SessionFactoryImpl.java:253)
com.WirTauschen.dao.ProductBasicDaoImpl.updateProduct(ProductBasicDaoImpl.java:50)
Controller :
#RequestMapping(value = "/product/addimages", method = RequestMethod.POST)
public #ResponseBody String addProductImages(#RequestParam("productImages") MultipartFile[] uploadedFiles){
if(uploadedFiles != null && uploadedFiles.length>0) {
for (MultipartFile uploadedFile : uploadedFiles) {
try {
if (!(uploadedFile.isEmpty())) {
imagesList.add(uploadedFile.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
return "image failed to upload";
}
}
}
return "done";
}
#RequestMapping(value="/product/add",method = RequestMethod.POST)
public String addProduct(#ModelAttribute("product") ProductBasic productBasic,Model model){
model.addAttribute("product", new ProductBasic());
productBasic.setProductimage(productprofileimage);
int productid = productBasicService.addProduct(productBasic);
ProductBasic productBasic1 = this.productBasicService.getProductById(productid);
for (int index = 0; index < imagesList.size(); index++) {
if (index == 0) {
productBasic1.setProductimage(imagesList.get(0));
}
ProductImage productImage = new ProductImage();
productImage.setProductimage(imagesList.get(index));
this.productImageService.addProductImage(productBasic1, productImage);
}
productBasicService.updateProduct(productBasic1);
imagesList.clear();
productprofileimage =null;
return "redirect:/product/show";
}
ProductDAOImpl :
#Override
#Transactional
public int addProduct(User user, ProductBasic productBasic) {
// I was using getSessionBefore with Eager, it worked, thought of trying openSession
session = this.sessionFactory.openSession();
user.getProductBasics().add(productBasic);
productBasic.setUser1(user);
session.save(productBasic);
System.out.println("Returned product information is"+productBasic.getProductid());
session.flush();
//session.close();
return productBasic.getProductid();
}
#Override
#Transactional
public void updateProduct(User user,ProductBasic productBasic) {
logger.debug("Editing product information");
session = this.sessionFactory.getCurrentSession();
// User user1 = (User) session.get(User.class,id);
user.getProductBasics().add(productBasic);
productBasic.setUser1(user);
session.saveOrUpdate(productBasic);
session.flush();
}
ProductImageDAOImpl :
#Override
#Transactional
public boolean addProductImage(ProductBasic productBasic,ProductImage productImage) {
session = sessionFactory.openSession();
productBasic.getProductImageSet().add(productImage);
productImage.setProductimageupload(productBasic);
productBasic.setImagecount((productBasic.getImagecount()+1));
session.merge(productBasic);
session.saveOrUpdate(productImage);
return true;
}
The controller code shouldn't contain that much service side information, but this is just an attempt to make sure it works..I have defined LazyLoading in model, I can post the code if required, kindly tell me what am I doing wrong. Any pointers are welcome. Thank you for your time.
Extension to my comments
Remove the saveOrUpdate above.
Change the cascade type to ALL
PS: You got exception at UpdateProduct method. but you hve posted code for addProduct.

spring security LDAP get additional fields

I am using Spring Security with LDAP (Active directory), I am able to authenticate user and create my own user detail object by extending LdapUserDetailsMapper.
By default I am getting certain fields and groups and DN.
But I would like to get additional fields, like email, contact number, which are available in Active Directory.
So how to get those information ?
My configuration
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("hmie.co.in", "ldap://1.1.1.1:389/");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper);
return provider;
}
Custom user detail mapping
#Service
public class MyUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
MyUserDetails myUserDetails = new MyUserDetails();
myUserDetails.setAccountNonExpired(ldapUserDetailsImpl.isAccountNonExpired());
myUserDetails.setAccountNonLocked(ldapUserDetailsImpl.isAccountNonLocked());
myUserDetails.setCredentialsNonExpired(ldapUserDetailsImpl.isCredentialsNonExpired());
myUserDetails.setEnabled(ldapUserDetailsImpl.isEnabled());
myUserDetails.setUsername(ldapUserDetailsImpl.getUsername());
myUserDetails.setAuthorities(ldapUserDetailsImpl.getAuthorities());
String dn = ldapUserDetailsImpl.getDn();
int beginIndex = dn.indexOf("cn=") + 3;
int endIndex = dn.indexOf(",");
myUserDetails.setEmployeeName(dn.substring(beginIndex, endIndex));
beginIndex = dn.indexOf("ou=") + 3;
endIndex = dn.indexOf(",", beginIndex);
myUserDetails.setDepartment(dn.substring(beginIndex, endIndex));
return myUserDetails;
}
}
To get the complete LDAP Directory attributes and values i did like this. But here i am using inteface org.springframework.ldap.core.AttributesMapper instead of class org.springframework.security.ldap.userdetails.LdapUserDetailsMapper.
ldapTemplate.search("o=XXXXX", new EqualsFilter("uid", userName).encode(),
new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attr) throws NamingException {
// TODO Auto-generated method stub
NamingEnumeration<String> namingEnumeration = attr.getIDs();
while (namingEnumeration.hasMoreElements()) {
String attributeName= (String) namingEnumeration.nextElement();
System.out.println(attributeName+" = "+attr.get(attributeName));
}
return null;
}
});
In the above piece of code attr.getIDs() returns the Active directory attributes like CN,DN,SN and mail. attr.get(attribute) returns the value of attribute.
The code in mapUserFromContext is so close! The key detail is that the ctx object passed in to the method already contains the additional Active Directory attributes for the principal. The attribute values are accessible using method ctx.getStringAttribute("attribute-name"). For example, you would access the surname attribute of the principal with ctx.getStringAttribute("sn"). To get the user's email and contact number, you would only need to access the appropriate attributes. In my company's Active Directory, those attributes are mail and phone, respectively. The attributes might be named differently in your system.

How update/remove an item already cached within a collection of items

I am working with Spring and EhCache
I have the following method
#Override
#Cacheable(value="products", key="#root.target.PRODUCTS")
public Set<Product> findAll() {
return new LinkedHashSet<>(this.productRepository.findAll());
}
I have other methods working with #Cacheable and #CachePut and #CacheEvict.
Now, imagine the database returns 100 products and they are cached through key="#root.target.PRODUCTS", then other method would insert - update - deleted an item into the database. Therefore the products cached through the key="#root.target.PRODUCTS" are not the same anymore such as the database.
I mean, check the two following two methods, they are able to update/delete an item, and that same item is cached in the other key="#root.target.PRODUCTS"
#Override
#CachePut(value="products", key="#product.id")
public Product update(Product product) {
return this.productRepository.save(product);
}
#Override
#CacheEvict(value="products", key="#id")
public void delete(Integer id) {
this.productRepository.delete(id);
}
I want to know if is possible update/delete the item located in the cache through the key="#root.target.PRODUCTS", it would be 100 with the Product updated or 499 if the Product was deleted.
My point is, I want avoid the following:
#Override
#CachePut(value="products", key="#product.id")
#CacheEvict(value="products", key="#root.target.PRODUCTS")
public Product update(Product product) {
return this.productRepository.save(product);
}
#Override
#Caching(evict={
#CacheEvict(value="products", key="#id"),
#CacheEvict(value="products", key="#root.target.PRODUCTS")
})
public void delete(Integer id) {
this.productRepository.delete(id);
}
I don't want call again the 500 or 499 products to be cached into the key="#root.target.PRODUCTS"
Is possible do this? How?
Thanks in advance.
Caching the collection using the caching abstraction is a duplicate of what the underlying caching system is doing. And because this is a duplicate, it turns out that you have to resort to some kind of duplications in your own code in one way or the other (the duplicate key for the set is the obvious representation of that). And because there is duplication, you have to sync state somehow
If you really need to access to the whole set and individual elements, then you should probably use a shortcut for the easiest leg. First, you should make sure your cache contains all elements which is not something that is obvious. Far from it actually. Considering you have that:
//EhCacheCache cache = (EhCacheCache) cacheManager.getCache("products");
#Override
public Set<Product> findAll() {
Ehcache nativeCache = cache.getNativeCache();
Map<Object, Element> elements = nativeCache.getAll(nativeCache.getKeys());
Set<Product> result = new HashSet<Product>();
for (Element element : elements.values()) {
result.add((Product) element.getObjectValue());
}
return Collections.unmodifiableSet(result);
}
The elements result is actually a lazy loaded map so a call to values() may throw an exception. You may want to loop over the keys or something.
You have to remember that the caching abstraction eases the access to the underlying caching infrastructure and in no way it replaces it: if you had to use the API directly, this is probably what you would have to do in some sort.
Now, we can keep the conversion on SPR-12036 if you believe we can improve the caching abstraction in that area. Thanks!
I think something like this schould work... Actually it's only a variation if "Stéphane Nicoll" answer ofcourse, but it may be useful for someone. I write it right here and haven't check it in IDE, but something similar works in my Project.
Override CacheResolver:
#Cacheable(value="products", key="#root.target.PRODUCTS", cacheResolver = "customCacheResolver")
Implement your own cache resolver, which search "inside" you cached items and do the work in there
public class CustomCacheResolver implements CacheResolver{
private static final String CACHE_NAME = "products";
#Autowired(required = true) private CacheManager cacheManager;
#SuppressWarnings("unchecked")
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> cacheOperationInvocationContext) {
// 1. Take key from query and create new simple key
SimpleKey newKey;
if (cacheOperationInvocationContext.getArgs().length != null) { //optional
newKey = new SimpleKey(args); //It's the key of cached object, which your "#Cachable" search for
} else {
//Schould never be... DEFAULT work with cache if something wrong with arguments
return new ArrayList<>(Arrays.asList(cacheManager.getCache(CACHE_NAME)));
}
// 2. Take cache
EhCacheCache ehCache = (EhCacheCache)cacheManager.getCache(CACHE_NAME); //this one we bringing back
Ehcache cache = (Ehcache)ehCache.getNativeCache(); //and with this we working
// 3. Modify existing Cache if we have it
if (cache.getKeys().contains(newKey) && YouWantToModifyIt) {
Element element = cache.get(key);
if (element != null && !((List<Products>)element.getObjectValue()).isEmpty()) {
List<Products> productsList = (List<Products>)element.getObjectValue();
// ---**--- Modify your "productsList" here as you want. You may now Change single element in this list.
ehCache.put(key, anfragenList); //this method NOT adds cache, but OVERWRITE existing
// 4. Maybe "Create" new cache with this key if we don't have it
} else {
ehCache.put(newKey, YOUR_ELEMENTS);
}
return new ArrayList<>(Arrays.asList(ehCache)); //Bring all back - our "new" or "modified" cache is in there now...
}
Read more about CRUD of EhCache: EhCache code samples
Hope it helps. And sorry for my English:(
I think there is a way to read the collection from underlying cache structure of spring. You can retrieve the collection from underlying ConcurrentHashMap as key-value pairs without using EhCache or anything else. Then you can update or remove an entry from that collection and then you can update the cache too. Here is an example that may help:
import com.crud.model.Post;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.stereotype.Component;
import java.util.*;
#Component
#Slf4j
public class CustomCacheResolver implements CacheResolver {
private static final String CACHE_NAME = "allPost";
#Autowired
private CacheManager cacheManager;
#SuppressWarnings("unchecked")
#Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> cacheOperationInvocationContext) {
log.info(Arrays.toString(cacheOperationInvocationContext.getArgs()));
String method = cacheOperationInvocationContext.getMethod().toString();
Post post = null;
Long postId = null;
if(method.contains("update")) {
//get the updated post
Object[] args = cacheOperationInvocationContext.getArgs();
post = (Post) args[0];
}
else if(method.contains("delete")){
//get the post Id to delete
Object[] args = cacheOperationInvocationContext.getArgs();
postId = (Long) args[0];
}
//read the cache
Cache cache = cacheManager.getCache(CACHE_NAME);
//get the concurrent cache map in key-value pair
assert cache != null;
Map<SimpleKey, List<Post>> map = (Map<SimpleKey, List<Post>>) cache.getNativeCache();
//Convert to set to iterate
Set<Map.Entry<SimpleKey, List<Post>>> entrySet = map.entrySet();
Iterator<Map.Entry<SimpleKey, List<Post>>> itr = entrySet.iterator();
//if a iterated entry is a list then it is our desired data list!!! Yayyy
Map.Entry<SimpleKey, List<Post>> entry = null;
while (itr.hasNext()){
entry = itr.next();
if(entry instanceof List) break;
}
//get the list
assert entry != null;
List<Post> postList = entry.getValue();
if(method.contains("update")) {
//update it
for (Post temp : postList) {
assert post != null;
if (temp.getId().equals(post.getId())) {
postList.remove(temp);
break;
}
}
postList.add(post);
}
else if(method.contains("delete")){
//delete it
for (Post temp : postList) {
if (temp.getId().equals(postId)) {
postList.remove(temp);
break;
}
}
}
//update the cache!! :D
cache.put(entry.getKey(),postList);
return new ArrayList<>(Collections.singletonList(cacheManager.getCache(CACHE_NAME)));
}
}
Here are the methods that uses the CustomCacheResolver
#Cacheable(key = "{#pageNo,#pageSize}")
public List<Post> retrieveAllPost(int pageNo,int pageSize){ // return list}
#CachePut(key = "#post.id",cacheResolver = "customCacheResolver")
public Boolean updatePost(Post post, UserDetails userDetails){ //your logic}
#CachePut(key = "#postId",cacheResolver = "customCacheResolver")
public Boolean deletePost(Long postId,UserDetails userDetails){ // your logic}
#CacheEvict(allEntries = true)
public Boolean createPost(String userId, Post post){//your logic}
Hope it helps to manipulate your spring application cache manually!
Though I don't see any easy way, but you can override Ehcache cache functionality by supplying cache decorator. Most probably you'd want to use EhcahceDecoratorAdapter, to enhance functions used by EhCacheCache put and evict methods.
Simple and rude solution is :
#Cacheable(key = "{#pageNo,#pageSize}")
public List<Post> retrieveAllPost(int pageNo,int pageSize){ // return list}
#CacheEvict(allEntries = true)
public Boolean updatePost(Post post, UserDetails userDetails){ //your logic}
#CacheEvict(allEntries = true)
public Boolean deletePost(Long postId,UserDetails userDetails){ // your logic}
#CacheEvict(allEntries = true)
public Boolean createPost(String userId, Post post){//your logic}

Resources