Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Eclipse Scout » Cache example
Cache example [message #1828065] Sun, 31 May 2020 12:41 Go to next message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
I was playing with cache, I managed to make it work, however I could use alternative to ICacheValueResolver way of storing data in cache.

I want to cache data from DB/LDAP, using specific connection (provided at the time of querying cache). At this moment I can provide connection in constructor for ValueResolver, but that is for the lifetime of cache object, so that is super inconvenient...

What I am thinking:
1. outsource problem to connection pooling - one extra connection once a while should be acceptable
2. extend get and resolve methods from ICache and ICacheValueResolver to accommodate connections
3. extend ICache with put method

And now I know what I am going to do. Thank you.
(2 will be very messy and 3 kinda messy, so I will have to stomach 1)

BTW. What withAdditionalCustomWrapper() is for in CacheBuilder class?
Any other tips on Scout caches are also welcomed.

		String cacheId = "mycache";
//		CacheRegistryService crs = new CacheRegistryService();
		CacheRegistryService crs = BEANS.get(CacheRegistryService.class);
		
		ICache<String, String> cache = crs.opt(cacheId);
		
		if (cache != null) {
			LOG.debug("getting keys from cache");
			LOG.debug("key1: {}", cache.get("key1"));
			LOG.debug("key2: {}", cache.get("key2"));
			
		} else {
			LOG.debug("creating cache");
			CacheBuilder<String, String> cb = new CacheBuilder<String, String>();
			cache = cb
					.withCacheId(cacheId)
					.withTimeToLive(Long.valueOf("3600"), TimeUnit.SECONDS, false)
					.withValueResolver(new ValueResolver<String, String>())
					.build();
			
			LOG.debug("adding keys to cache");
			LOG.debug("key1: {}", cache.get("key1"));
			LOG.debug("key2: {}", cache.get("key2"));
		}

public class ValueResolver<K, V> implements ICacheValueResolver<K, V> {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(ValueResolver.class);
	
	public ValueResolver() {
		LOG.entry();
		
		LOG.exit();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public V resolve(K key) {
		LOG.entry(key);
		
		String result = key + "value";
		
		return (V) LOG.exit(result);
	}

}

Re: Cache example [message #1828145 is a reply to message #1828065] Tue, 02 June 2020 14:49 Go to previous messageGo to next message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
Road to nowhere part 2.

Previous example worked because I was caching all groups from LDAP (emphasis on ALL).
When you want to cache something that is not ALL you need a context...

I looked at BasicCache class and if only method getResolver() was declared as public there would be a way to initialize the ValueResolver, but unfortunately it is protected.
Let's try overriding it:

package io.poc.scout.authentication;

import java.util.Map;

import org.eclipse.scout.rt.platform.cache.BasicCache;
import org.eclipse.scout.rt.platform.cache.ICacheValueResolver;

public class MyBasicCache<K, V> extends BasicCache<K, V> {

	public MyBasicCache(String cacheId, ICacheValueResolver<K, V> resolver, Map<K, V> cacheMap,
			boolean atomicInsertion) {
		super(cacheId, resolver, cacheMap, atomicInsertion);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public ICacheValueResolver<K, V> getResolver() {
		return super.getResolver();
	}

}


CacheBuilder change:
				.withAdditionalCustomWrapper(MyBasicCache.class)



But that ends up with NullPointerException on CacheBuilder.build(). Why?
IMHO, this looks like a bug...

		// it is:
		withAdditionalCustomWrapper(Class<? extends ICache> cacheClass, Object...arguments)
		
		// expected
		withAdditionalCustomWrapper(Class<? extends AbstractCacheWrapper> cacheClass, Object...arguments)



So, that ended up with dead end...
I am not desperate enough to re-implement entire BasicCache class. (yet)

Re: Cache example [message #1828146 is a reply to message #1828145] Tue, 02 June 2020 14:51 Go to previous messageGo to next message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
Road to nowhere part 3.

I went through test cases for cache on github, and the ValueResolver can be anonymous class...
This works, but only in one thread. This is not thread safe.
Another dead end...

package io.poc.scout.authentication;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.cache.CacheRegistryService;
import org.eclipse.scout.rt.platform.cache.ICache;
import org.eclipse.scout.rt.platform.cache.ICacheBuilder;
import org.eclipse.scout.rt.platform.cache.ICacheValueResolver;
import org.eclipse.scout.rt.platform.cache.KeyCacheEntryFilter;
import org.eclipse.scout.rt.platform.util.Assertions;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class DoSomething {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(DoSomething.class);
	
	
	private String cacheId = "DoSomething.cache";
	private ICache<String, Integer> cache = null;
	private int tmpInt = 0;
	
	
	public DoSomething() {
		LOG.entry();
		cache = getCache();
		LOG.exit();
	}
	
	
	public void doSomething(String ip) {
		LOG.entry(ip);
		
		Integer count = cache.get(ip);
		LOG.debug("ip: {} count: {}", ip, count);
		
		// remove old key
		dumpCache(cache);
		KeyCacheEntryFilter<String, Integer> filter = new KeyCacheEntryFilter<String, Integer>(Collections.singletonList(ip));
		cache.invalidate(filter, false);
		dumpCache(cache);
		
		tmpInt = count + 1;
		count = cache.get(ip);
		LOG.debug("ip: {} count: {}", ip, count);
		dumpCache(cache);
		
		LOG.exit();
	}

	private void dumpCache(ICache<String, Integer> cache) {
		LOG.entry(cache);
		
		Assertions.assertNotNull(cache);
		LOG.debug("cache content for: {}", cache.getCacheId());
		cache.getUnmodifiableMap().entrySet().stream().forEach(x -> LOG.debug("key: {} value: {}", x.getKey(), x.getValue()) );
		
		LOG.exit();
	}
	
	
	private ICache<String, Integer> createCache(ICacheValueResolver<String, Integer> resolver) {
		LOG.entry();
		ICache<String, Integer> result = null;
		
		@SuppressWarnings("unchecked")
		ICacheBuilder<String, Integer> cacheBuilder = BEANS.get(ICacheBuilder.class);
		result = cacheBuilder
				.withCacheId(cacheId)
				.withTimeToLive(Long.valueOf("1440"), TimeUnit.MINUTES, false)
				.withValueResolver(resolver)
				.build();
		
		return LOG.exit(result);
	}
	
	
	private ICache<String, Integer> createCache() {
		LOG.entry();
		
		return LOG.exit(createCache(new ICacheValueResolver<String, Integer>() {
			
			@Override
			public Integer resolve(String key) {
				LOG.entry(key);
				Integer result = null;
				
				if (tmpInt != 0) {
					result =  tmpInt;
					tmpInt = 0;
				} else {
					result = 0;
				}
				return LOG.exit(result);
			}
		} ) );
	}
	
	
	private ICache<String, Integer> getCache() {
		LOG.entry();
		ICache<String, Integer> result = null;
		
		CacheRegistryService crs = BEANS.get(CacheRegistryService.class);
		result = crs.opt(cacheId);
		
		if (result == null) {
			result = createCache();
		}
		
		return LOG.exit(result);
	}
	
	
}
Re: Cache example [message #1828147 is a reply to message #1828146] Tue, 02 June 2020 15:02 Go to previous message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
Victory lap?

I really want to avoid re-implementation of BasicCache, and that is with assumption that CacheBuilder.withAdditionalCustomWrapper() works the way I think it is working. If not that would mean re-implementation of CacheBuilder too...

And than it hit me, what if I poke with the Key Object: who said it has to be String? What if I use it to encapsulate whatever needs to be passed to ValueResolver...
But as a key, proper implementations of hashCode() and equals() has to be provided.

This way even ValueResolver does not have to be an anonymous class (I am not a big fan of those)

What I will be passing to ValueResolver?
* previous value. If I am caching number of events
* connection/session (db, ldap). Usually at the time of getting data from cache I already have working connection/session
* context of what I am getting, i.e. ldap dn of user or group. Getting everything is unusual...

Below is working example. It is AccessControler with "ban" option after specified number of failed login attempts. Ban is for IP address for a specified amount of time. Cache is used to store number of failed login attempts.

If I have missed something, or there is a better way of using scout caches, I'd be happy to read some comments.

KeyContexObject
package io.poc.scout.authentication;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class KeyContexObject {
	
	
	private String key = null;
	private Integer previousValue = null;
	private Object ctx = null;
	
	
	public KeyContexObject(String key) {
		super();
		this.key = key;
	}
	
	
	public String getKey() {
		return key;
	}
	
	public Integer getPreviousValue() {
		return previousValue;
	}
	
	public void setPreviousValue(Integer previousValue) {
		this.previousValue = previousValue;
	}
	
	public Object getCtx() {
		return ctx;
	}
	
	public void setCtx(Object ctx) {
		this.ctx = ctx;
	}
	
	
	@Override
	public String toString() {
		return "KeyContexObject [key=" + key + "]";
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(307, 311).append(key).toHashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) { return true; }
		if ( (obj == null) || (getClass() != obj.getClass()) ) { return false; }
		
		KeyContexObject other = (KeyContexObject) obj;
		
		return new EqualsBuilder()
				.append(key, other.getKey())
				.isEquals();
	}
	
	
}



MK3FormBasedAccessController
package io.poc.scout.authentication;

import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.cache.CacheRegistryService;
import org.eclipse.scout.rt.platform.cache.ICache;
import org.eclipse.scout.rt.platform.cache.ICacheBuilder;
import org.eclipse.scout.rt.platform.cache.ICacheValueResolver;
import org.eclipse.scout.rt.platform.cache.KeyCacheEntryFilter;
import org.eclipse.scout.rt.platform.security.ICredentialVerifier;
import org.eclipse.scout.rt.platform.util.Assertions;
import org.eclipse.scout.rt.platform.util.Pair;
import org.eclipse.scout.rt.server.commons.authentication.FormBasedAccessController;
import org.eclipse.scout.rt.server.commons.authentication.ServletFilterHelper;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class MK3FormBasedAccessController extends FormBasedAccessController {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(MK3FormBasedAccessController.class);
	
	
	private final String cacheId = "MK3FormBasedAccessController.bruteforceCache";
	private boolean bruteforcePreventionEnabled = false;
	
	private String bruteforcePrevention = null;
	private String bruteforceLockout = null;
	private int bruteforcePreventionInt = 0;
	
	
	public MK3FormBasedAccessController() {
		LOG.entry();
		
//		bruteforcePrevention = CONFIG.getPropertyValue(BruteforcePreventionProperty.class);
//		bruteforceLockout = CONFIG.getPropertyValue(BruteforceLockoutProperty.class);
		bruteforcePrevention = "10";  // ban ip after this many failures
		bruteforceLockout = "1440";  // lockout in minutes
		
		bruteforcePreventionInt = Integer.valueOf(bruteforcePrevention);
		
		if (bruteforcePreventionInt > 0) {
			bruteforcePreventionEnabled = true;
		}
		
		LOG.debug("bruteforcePreventionEnabled: {}", bruteforcePreventionEnabled);
		LOG.debug("bruteforcePreventionInt: {}", bruteforcePreventionInt);
		LOG.debug("bruteforceLockout: {}", bruteforceLockout);
		
		LOG.exit();
	}
	
	
	@Override
	protected boolean handleAuthRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
		LOG.entry();
		
		// Never cache authentication requests.
		response.setHeader("Cache-Control", "private, no-store, no-cache, max-age=0"); // HTTP 1.1
		response.setHeader("Pragma", "no-cache"); // HTTP 1.0
		response.setDateHeader("Expires", 0); // prevents caching at the proxy server
		
		
		ICache<KeyContexObject, Integer> cache = getCache();
		
		if (bruteforcePreventionEnabled && bruteforcePreventionInEffect(request, cache)) {
			handleForbidden(ICredentialVerifier.AUTH_FORBIDDEN, response);
			return LOG.exit(true);
		}
		
		final Pair<String, char[]> credentials = readCredentials(request);
		if (credentials == null) {
			handleForbidden(ICredentialVerifier.AUTH_CREDENTIALS_REQUIRED, response);
			return LOG.exit(true);
		}
		
		final int status = m_config.getCredentialVerifier().verify(credentials.getLeft(), credentials.getRight());
		if (status != ICredentialVerifier.AUTH_OK) {
			if (bruteforcePreventionEnabled) bruteforcePreventionIncrement(request, cache);
			handleForbidden(status, response);
			return LOG.exit(true);
		}
		// reset counter on successful login here
		
		//OWASP: force a new HTTP session to be created.
		final HttpSession session = request.getSession(false);
		if (session != null) {
			session.invalidate();
		}
		
		// Put authenticated principal onto (new) HTTP session
		final Principal principal = m_config.getPrincipalProducer().produce(credentials.getLeft());
		BEANS.get(ServletFilterHelper.class).putPrincipalOnSession(request, principal);
		return LOG.exit(true);
	}
	
	
	private boolean bruteforcePreventionInEffect(final HttpServletRequest request, ICache<KeyContexObject, Integer> cache) {
		LOG.entry();
		boolean result = false;
		
		String ip = request.getRemoteAddr();
		KeyContexObject kco = new KeyContexObject(ip);
		// set context if needed. here we do not
		// kco.setCtx();
		Integer count = cache.get(kco);
		
		LOG.debug("ip: {} count: {}", ip, count);
		
		if (count >= bruteforcePreventionInt) {
			result = true;
		}
		
		return LOG.exit(result);
	}
	
	
	private void bruteforcePreventionIncrement(final HttpServletRequest request, ICache<KeyContexObject, Integer> cache) {
		LOG.entry();
		
		String ip = request.getRemoteAddr();
		KeyContexObject kco = new KeyContexObject(ip);
		Integer count = cache.get(kco);
		LOG.debug("ip: {} old count: {}", ip, count);
		
		// remove old key
		dumpCache(cache);
		KeyCacheEntryFilter<KeyContexObject, Integer> filter = new KeyCacheEntryFilter<KeyContexObject, Integer>(Collections.singletonList(kco));
		cache.invalidate(filter, false);
		dumpCache(cache);
		
		// update cache with incremented value
		kco.setPreviousValue(count);
		count = cache.get(kco);
		LOG.debug("ip: {} new count: {}", ip, count);
		dumpCache(cache);
		
		LOG.exit();
	}
	
	
	private void dumpCache(ICache<KeyContexObject, Integer> cache) {
		LOG.entry(cache);
		
		Assertions.assertNotNull(cache);
		LOG.debug("cache content for: {}", cache.getCacheId());
		cache.getUnmodifiableMap().entrySet().stream().forEach(x -> LOG.debug("key: {} value: {}", x.getKey().getKey(), x.getValue()) );
		
		LOG.exit();
	}
	
	
	private ICache<KeyContexObject, Integer> createCache(ICacheValueResolver<KeyContexObject, Integer> resolver) {
		LOG.entry();
		ICache<KeyContexObject, Integer> result = null;
		
		@SuppressWarnings("unchecked")
		ICacheBuilder<KeyContexObject, Integer> cacheBuilder = BEANS.get(ICacheBuilder.class);
		result = cacheBuilder
				.withCacheId(cacheId)
				.withTimeToLive(Long.valueOf(bruteforceLockout), TimeUnit.MINUTES, false)
				.withValueResolver(resolver)
				.withThreadSafe(true)
				.build();
		
		return LOG.exit(result);
	}
	
	
	private ICache<KeyContexObject, Integer> createCache() {
		LOG.entry();
		
		return LOG.exit(createCache(new ICacheValueResolver<KeyContexObject, Integer>() {
			
			@Override
			public Integer resolve(KeyContexObject key) {
				LOG.entry(key);
				Integer result = null;
				
				// we have access to all the context we need
//				key.getPreviousValue();
//				key.getCtx();
				
				// increment counter
				if (key.getPreviousValue() != null) {
					result = key.getPreviousValue() + 1;
					
				} else {
					result = 0;
				}
				
				return LOG.exit(result);
			}
		} ) );
	}
	
	private ICache<KeyContexObject, Integer> getCache() {
		LOG.entry();
		ICache<KeyContexObject, Integer> result = null;
		
		CacheRegistryService crs = BEANS.get(CacheRegistryService.class);
		result = crs.opt(cacheId);
		
		if (result == null) {
			result = createCache();
		}
		
		return LOG.exit(result);
	}
	
	
}


Previous Topic:Store Cookie in AccessController
Next Topic:Cannot read property of null Error
Goto Forum:
  


Current Time: Fri Apr 19 11:38:46 GMT 2024

Powered by FUDForum. Page generated in 0.02014 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top