提交 fbac10e7 编写于 作者: J Joram Barrez

ACT-1696: introduce ldap group caching

上级 49ec078f
......@@ -22,7 +22,7 @@ import org.activiti.engine.impl.persistence.AbstractManager;
/**
* @author Tom Baeyens
*/
public class MembershipEntityManager extends AbstractManager {
public class MembershipEntityManager extends AbstractManager implements MembershipIdentityManager {
public void createMembership(String userId, String groupId) {
MembershipEntity membershipEntity = new MembershipEntity();
......
......@@ -59,6 +59,10 @@ public class LDAPConfigurator implements ProcessEngineConfigurator {
// Pluggable query helper bean
protected LDAPQueryBuilder ldapQueryBuilder = new LDAPQueryBuilder();
// Group caching
protected int groupCacheSize = -1;
protected long groupCacheExpirationTime = 3600000L; // default: one hour
public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
LDAPUserManagerFactory ldapUserManagerFactory = getLdapUserManagerFactory();
processEngineConfiguration.getSessionFactories().put(ldapUserManagerFactory.getSessionType(), ldapUserManagerFactory);
......@@ -251,5 +255,21 @@ public class LDAPConfigurator implements ProcessEngineConfigurator {
public void setLdapQueryBuilder(LDAPQueryBuilder ldapQueryBuilder) {
this.ldapQueryBuilder = ldapQueryBuilder;
}
public int getGroupCacheSize() {
return groupCacheSize;
}
public void setGroupCacheSize(int groupCacheSize) {
this.groupCacheSize = groupCacheSize;
}
public long getGroupCacheExpirationTime() {
return groupCacheExpirationTime;
}
public void setGroupCacheExpirationTime(long groupCacheExpirationTime) {
this.groupCacheExpirationTime = groupCacheExpirationTime;
}
}
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.ldap;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.identity.Group;
import org.activiti.engine.impl.util.ClockUtil;
/**
* @author Joram Barrez
*/
public class LDAPGroupCache {
protected Map<String, LDAPGroupCacheEntry> groupCache;
protected long expirationTime;
protected LDAPGroupCacheListener ldapCacheListener;
public LDAPGroupCache(final int cacheSize, final long expirationTime) {
// From http://stackoverflow.com/questions/224868/easy-simple-to-use-lru-cache-in-java
this.groupCache =new LinkedHashMap<String, LDAPGroupCache.LDAPGroupCacheEntry>(cacheSize + 1, 0.75f, true) {
private static final long serialVersionUID = 5207574193173514579L;
protected boolean removeEldestEntry(java.util.Map.Entry<String, LDAPGroupCacheEntry> eldest) {
boolean removeEldest = size() > cacheSize;
if (removeEldest && ldapCacheListener != null) {
ldapCacheListener.cacheEviction(eldest.getKey());
}
return removeEldest;
}
};
this.expirationTime = expirationTime;
}
public void add(String userId, List<Group> groups) {
this.groupCache.put(userId, new LDAPGroupCacheEntry(ClockUtil.getCurrentTime(), groups));
}
public List<Group> get(String userId) {
LDAPGroupCacheEntry cacheEntry = groupCache.get(userId);
if (cacheEntry != null) {
if ((ClockUtil.getCurrentTime().getTime() - cacheEntry.getTimestamp().getTime()) < expirationTime) {
if (ldapCacheListener != null) {
ldapCacheListener.cacheHit(userId);
}
return cacheEntry.getGroups();
} else {
this.groupCache.remove(userId);
if (ldapCacheListener != null) {
ldapCacheListener.cacheExpired(userId);
ldapCacheListener.cacheEviction(userId);
}
}
}
if (ldapCacheListener != null) {
ldapCacheListener.cacheMiss(userId);
}
return null;
}
public void clear() {
groupCache.clear();
}
public Map<String, LDAPGroupCacheEntry> getGroupCache() {
return groupCache;
}
public void setGroupCache(Map<String, LDAPGroupCacheEntry> groupCache) {
this.groupCache = groupCache;
}
public long getExpirationTime() {
return expirationTime;
}
public void setExpirationTime(long expirationTime) {
this.expirationTime = expirationTime;
}
public LDAPGroupCacheListener getLdapCacheListener() {
return ldapCacheListener;
}
public void setLdapCacheListener(LDAPGroupCacheListener ldapCacheListener) {
this.ldapCacheListener = ldapCacheListener;
}
// Helper classes ////////////////////////////////////
static class LDAPGroupCacheEntry {
protected Date timestamp;
protected List<Group> groups;
public LDAPGroupCacheEntry() {
}
public LDAPGroupCacheEntry(Date timestamp, List<Group> groups) {
this.timestamp = timestamp;
this.groups = groups;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
}
// Cache listeners. Currently not yet exposed (only programmatically for the moment)
// Experimental stuff!
public static interface LDAPGroupCacheListener {
void cacheHit(String userId);
void cacheMiss(String userId);
void cacheEviction(String userId);
void cacheExpired(String userId);
}
}
......@@ -37,10 +37,16 @@ import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
public class LDAPGroupManager extends AbstractManager implements GroupIdentityManager {
protected LDAPConfigurator ldapConfigurator;
protected LDAPGroupCache ldapGroupCache;
public LDAPGroupManager(LDAPConfigurator ldapConfigurator) {
this.ldapConfigurator = ldapConfigurator;
}
public LDAPGroupManager(LDAPConfigurator ldapConfigurator, LDAPGroupCache ldapGroupCache) {
this.ldapConfigurator = ldapConfigurator;
this.ldapGroupCache = ldapGroupCache;
}
@Override
public Group createNewGroup(String groupId) {
......@@ -79,6 +85,16 @@ public class LDAPGroupManager extends AbstractManager implements GroupIdentityMa
@Override
public List<Group> findGroupsByUser(final String userId) {
// First try the cache (if one is defined)
if (ldapGroupCache != null) {
List<Group> groups = ldapGroupCache.get(userId);
if (groups != null) {
return groups;
}
}
// Do the search against Ldap
LDAPTemplate ldapTemplate = new LDAPTemplate(ldapConfigurator);
return ldapTemplate.execute(new LDAPCallBack<List<Group>>() {
......@@ -106,6 +122,12 @@ public class LDAPGroupManager extends AbstractManager implements GroupIdentityMa
}
namingEnum.close();
// Cache results for later
if (ldapGroupCache != null) {
ldapGroupCache.add(userId, groups);
}
return groups;
} catch (NamingException e) {
......
......@@ -15,6 +15,7 @@ package org.activiti.ldap;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.activiti.ldap.LDAPGroupCache.LDAPGroupCacheListener;
/**
* @author Joram Barrez
......@@ -23,8 +24,18 @@ public class LDAPGroupManagerFactory implements SessionFactory {
protected LDAPConfigurator ldapConfigurator;
protected LDAPGroupCache ldapGroupCache;
protected LDAPGroupCacheListener ldapCacheListener;
public LDAPGroupManagerFactory(LDAPConfigurator ldapConfigurator) {
this.ldapConfigurator = ldapConfigurator;
if (ldapConfigurator.getGroupCacheSize() > 0) {
ldapGroupCache = new LDAPGroupCache(ldapConfigurator.getGroupCacheSize(), ldapConfigurator.getGroupCacheExpirationTime());
if (ldapCacheListener != null) {
ldapGroupCache.setLdapCacheListener(ldapCacheListener);
}
}
}
@Override
......@@ -34,7 +45,11 @@ public class LDAPGroupManagerFactory implements SessionFactory {
@Override
public Session openSession() {
return new LDAPGroupManager(ldapConfigurator);
if (ldapGroupCache == null) {
return new LDAPGroupManager(ldapConfigurator);
} else {
return new LDAPGroupManager(ldapConfigurator, ldapGroupCache);
}
}
public LDAPConfigurator getLdapConfigurator() {
......@@ -44,5 +59,21 @@ public class LDAPGroupManagerFactory implements SessionFactory {
public void setLdapConfigurator(LDAPConfigurator ldapConfigurator) {
this.ldapConfigurator = ldapConfigurator;
}
public LDAPGroupCache getLdapGroupCache() {
return ldapGroupCache;
}
public void setLdapGroupCache(LDAPGroupCache ldapGroupCache) {
this.ldapGroupCache = ldapGroupCache;
}
public LDAPGroupCacheListener getLdapCacheListener() {
return ldapCacheListener;
}
public void setLdapCacheListener(LDAPGroupCacheListener ldapCacheListener) {
this.ldapCacheListener = ldapCacheListener;
}
}
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.test.ldap;
import java.util.Date;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.activiti.engine.impl.util.ClockUtil;
import org.activiti.engine.test.Deployment;
import org.activiti.ldap.LDAPGroupCache;
import org.activiti.ldap.LDAPGroupCache.LDAPGroupCacheListener;
import org.activiti.ldap.LDAPGroupManagerFactory;
import org.activiti.spring.impl.test.SpringActivitiTestCase;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration("classpath:activiti-context-ldap-group-cache.xml")
public class LdapGroupCacheTest extends SpringActivitiTestCase {
protected TestLDAPGroupCacheListener cacheListener;
@Override
protected void setUp() throws Exception {
super.setUp();
// Set test cache listener
LDAPGroupManagerFactory ldapGroupManagerFactory =
(LDAPGroupManagerFactory) processEngineConfiguration.getSessionFactories().get(GroupIdentityManager.class);
LDAPGroupCache ldapGroupCache = ldapGroupManagerFactory.getLdapGroupCache();
ldapGroupCache.clear();
cacheListener = new TestLDAPGroupCacheListener();
ldapGroupCache.setLdapCacheListener(cacheListener);
}
@Deployment
public void testLdapGroupCacheUsage() {
runtimeService.startProcessInstanceByKey("testLdapGroupCache");
// First task is for Kermit -> cache miss
assertEquals(1, taskService.createTaskQuery().taskCandidateUser("kermit").count());
assertEquals("kermit", cacheListener.getLastCacheMiss());
// Second task is for Pepe -> cache miss
taskService.complete(taskService.createTaskQuery().singleResult().getId());
assertEquals(1, taskService.createTaskQuery().taskCandidateUser("pepe").count());
assertEquals("pepe", cacheListener.getLastCacheMiss());
// Third task is again for kermit -> cache hit
taskService.complete(taskService.createTaskQuery().singleResult().getId());
assertEquals(1, taskService.createTaskQuery().taskCandidateUser("kermit").count());
assertEquals("kermit", cacheListener.getLastCacheHit());
// Foruth task is for fozzie -> cache miss + cache eviction of pepe (LRU)
taskService.complete(taskService.createTaskQuery().singleResult().getId());
assertEquals(1, taskService.createTaskQuery().taskCandidateUser("fozzie").count());
assertEquals("fozzie", cacheListener.getLastCacheMiss());
assertEquals("pepe", cacheListener.getLastCacheEviction());
}
public void testLdapGroupCacheExpiration() {
assertEquals(0, taskService.createTaskQuery().taskCandidateUser("kermit").count());
assertEquals("kermit", cacheListener.getLastCacheMiss());
assertEquals(0, taskService.createTaskQuery().taskCandidateUser("pepe").count());
assertEquals("pepe", cacheListener.getLastCacheMiss());
assertEquals(0, taskService.createTaskQuery().taskCandidateUser("kermit").count());
assertEquals("kermit", cacheListener.getLastCacheHit());
// Test the expiration time of the cache
Date now = new Date();
ClockUtil.setCurrentTime(now);
assertEquals(0, taskService.createTaskQuery().taskCandidateUser("fozzie").count());
assertEquals("fozzie", cacheListener.getLastCacheMiss());
assertEquals("pepe", cacheListener.getLastCacheEviction());
// Moving the clock forward two 45 minues should trigger cache eviction (configured to 30 mins)
ClockUtil.setCurrentTime(new Date(now.getTime() + (45 * 60 * 1000)));
assertEquals(0, taskService.createTaskQuery().taskCandidateUser("fozzie").count());
assertEquals("fozzie", cacheListener.getLastCacheExpiration());
assertEquals("fozzie", cacheListener.getLastCacheEviction());
assertEquals("fozzie", cacheListener.getLastCacheMiss());
}
// Test cache listener
static class TestLDAPGroupCacheListener implements LDAPGroupCacheListener {
protected String lastCacheMiss;
protected String lastCacheHit;
protected String lastCacheEviction;
protected String lastCacheExpiration;
public void cacheMiss(String userId) {
this.lastCacheMiss = userId;
}
public void cacheHit(String userId) {
this.lastCacheHit = userId;
}
public void cacheExpired(String userId) {
this.lastCacheExpiration = userId;
}
public void cacheEviction(String userId) {
this.lastCacheEviction = userId;
}
public String getLastCacheMiss() {
return lastCacheMiss;
}
public void setLastCacheMiss(String lastCacheMiss) {
this.lastCacheMiss = lastCacheMiss;
}
public String getLastCacheHit() {
return lastCacheHit;
}
public void setLastCacheHit(String lastCacheHit) {
this.lastCacheHit = lastCacheHit;
}
public String getLastCacheExpiration() {
return lastCacheExpiration;
}
public void setLastCacheExpiration(String lastCacheExpiration) {
this.lastCacheExpiration = lastCacheExpiration;
}
public String getLastCacheEviction() {
return lastCacheEviction;
}
public void setLastCacheEviction(String lastCacheEviction) {
this.lastCacheEviction = lastCacheEviction;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- Embedded ldap test server -->
<security:ldap-server ldif="classpath:users.ldif" root="o=activiti" manager-dn="uid=admin, ou=users" manager-password="admin"/>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="false" />
<property name="configurators">
<list>
<bean class="org.activiti.ldap.LDAPConfigurator">
<!-- Server connection params -->
<property name="server" value="ldap://localhost" />
<property name="port" value="33389" />
<property name="user" value="uid=admin, ou=users, o=activiti" />
<property name="password" value="pass" />
<!-- Query params -->
<property name="baseDn" value="o=activiti" />
<property name="queryUserByUserId" value="(&amp;(objectClass=inetOrgPerson)(uid={0}))" />
<property name="queryGroupsForUser" value="(&amp;(objectClass=groupOfUniqueNames)(uniqueMember={0}))" />
<!-- Attribute config -->
<property name="userFirstNameAttribute" value="cn" />
<property name="userLastNameAttribute" value="sn" />
<property name="groupIdAttribute" value="cn" />
<property name="groupNameAttribute" value="cn" />
<!-- Group cache settings -->
<property name="groupCacheSize" value="2" /> <!-- Setting it really low for testing purposes -->
<property name="groupCacheExpirationTime" value="1800000" />
</bean>
</list>
</property>
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<bean id="repositoryService" factory-bean="processEngine"
factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine"
factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine"
factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine"
factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine"
factory-method="getManagementService" />
</beans>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="taskAssigneeExample"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="ldap">
<process id="testLdapGroupCache">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask1" />
<userTask id="theTask1">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(Sales)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow2" sourceRef="theTask1" targetRef="theTask2" />
<userTask id="theTask2">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(pepe), group(Sales)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow3" sourceRef="theTask2" targetRef="theTask3" />
<userTask id="theTask3">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(Sales)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow4" sourceRef="theTask3" targetRef="theTask4" />
<userTask id="theTask4">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(fozzie), group(Sales)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow5" sourceRef="theTask3" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
......@@ -89,18 +89,15 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
</dependency>
</dependencies>
<profiles>
......
......@@ -699,13 +699,13 @@
<module>modules/activiti-webapp-explorer2</module>
<module>modules/activiti-rest</module>
<module>modules/activiti-webapp-rest2</module>
<module>modules/activiti-webapp-angular</module>
<module>modules/activiti-spring</module>
<module>modules/activiti-cxf</module>
<module>modules/activiti-mule</module>
<module>modules/activiti-camel</module>
<module>modules/activiti-cdi</module>
<module>modules/activiti-osgi</module>
<module>modules/activiti-ldap</module>
</modules>
<build>
<plugins>
......@@ -791,12 +791,12 @@
<module>modules/activiti-rest</module>
<module>modules/activiti-webapp-rest2</module>
<module>modules/activiti-webapp-explorer2</module>
<module>modules/activiti-webapp-angular</module>
<module>modules/activiti-cxf</module>
<module>modules/activiti-osgi</module>
<module>modules/activiti-camel</module>
<module>modules/activiti-mule</module>
<module>modules/activiti-cdi</module>
<module>modules/activiti-ldap</module>
</modules>
</profile>
<profile>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册