Index: kernel/deploy/shared/pom.xml =================================================================== --- kernel/deploy/shared/pom.xml (revision 306520) +++ kernel/deploy/shared/pom.xml (working copy) @@ -227,6 +227,16 @@ compile + org.terracotta + terracotta-toolkit-1.6-runtime + 5.6.0 + + + net.sf.ehcache + ehcache-terracotta + 2.6.6 + + org.mnode.ical4j ical4j compile @@ -238,6 +248,13 @@ + + + terracotta-releases + http://www.terracotta.org/download/reflector/releases + + + scm:svn:https://source.sakaiproject.org/svn/kernel/trunk/deploy/shared scm:svn:https://source.sakaiproject.org/svn/kernel/trunk/deploy/shared Index: kernel/kernel-component/src/main/webapp/WEB-INF/event-components.xml =================================================================== --- kernel/kernel-component/src/main/webapp/WEB-INF/event-components.xml (revision 306520) +++ kernel/kernel-component/src/main/webapp/WEB-INF/event-components.xml (working copy) @@ -39,6 +39,7 @@ + Index: kernel/kernel-component/src/main/webapp/WEB-INF/memory-components.xml =================================================================== --- kernel/kernel-component/src/main/webapp/WEB-INF/memory-components.xml (revision 306520) +++ kernel/kernel-component/src/main/webapp/WEB-INF/memory-components.xml (working copy) @@ -20,14 +20,14 @@ + + + classpath:org/sakaiproject/memory/api/ehcache.xml + + + - - - classpath:org/sakaiproject/memory/api/ehcache.xml - - - Index: kernel/kernel-impl/pom.xml =================================================================== --- kernel/kernel-impl/pom.xml (revision 306520) +++ kernel/kernel-impl/pom.xml (working copy) @@ -153,6 +153,24 @@ ehcache-core + org.terracotta + terracotta-toolkit-1.6-runtime + 5.6.0 + provided + + + net.sf.ehcache + ehcache-core + 2.6.6 + provided + + + net.sf.ehcache + ehcache-terracotta + 2.6.6 + provided + + javax.mail mail @@ -300,6 +318,12 @@ org.sakaiproject.emailtemplateservice emailtemplateservice-api + - + + + terracotta-releases + http://www.terracotta.org/download/reflector/releases + + Index: kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java =================================================================== --- kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (revision 306520) +++ kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (working copy) @@ -33,6 +33,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Observable; import java.util.Observer; import java.util.Set; @@ -41,6 +42,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.authz.api.*; +import org.sakaiproject.authz.api.SimpleRole; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.entity.api.Entity; @@ -569,10 +571,18 @@ if (realmRoleGRCache != null) { // KNL-1037 read the cached role and membership information - Map roles = realmRoleGRCache.get(REALM_ROLES_CACHE); + Map roles = new HashMap(); + + // dehydrate to SimpleRoles, which can be stored in a distributed Terracotta cache + Map roleProperties = realmRoleGRCache.get(REALM_ROLES_CACHE); + for (Entry mapEntry : roleProperties.entrySet()) { + roles.put(mapEntry.getKey(), new BaseRole(mapEntry.getValue())); + } Map userGrants = new HashMap(); - Map userGrantsWithRoleId = (Map) realmRoleGRCache.get(REALM_USER_GRANTS_CACHE); - userGrants.putAll(getMemberMap(userGrantsWithRoleId, roles)); + + Map userGrantsWithRoleIdMap = (Map) realmRoleGRCache.get(REALM_USER_GRANTS_CACHE); + userGrants.putAll(getMemberMap(userGrantsWithRoleIdMap, roles)); + realm.m_roles = roles; realm.m_userGrants = userGrants; } else { @@ -696,14 +706,18 @@ } }); - if (serverConfigurationService().getBoolean("authz.cacheGrants", true)) { - Map payLoad = new HashMap(); - - payLoad.put(REALM_ROLES_CACHE,realm.m_roles); - payLoad.put(REALM_USER_GRANTS_CACHE, getMemberWithRoleIdMap(realm.m_userGrants)); - - m_realmRoleGRCache.put(realm.getId(), payLoad); - } + if (serverConfigurationService().getBoolean("authz.cacheGrants", true)) { + Map payLoad = new HashMap(); + // rehydrate from SimpleRole, which can be stored in a Terracotta cache + Map roleProperties = new HashMap(); + for (Entry entry : ((Map) realm.m_roles).entrySet()) { + roleProperties.put(entry.getKey(), entry.getValue().exportToSimpleRole()); + } + Map membersWithRoleIds = getMemberWithRoleIdMap(realm.m_userGrants); + payLoad.put(REALM_ROLES_CACHE, roleProperties); + payLoad.put(REALM_USER_GRANTS_CACHE, membersWithRoleIds); + m_realmRoleGRCache.put(realm.getId(), payLoad); + } } } Index: kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/BaseEventTrackingService.java =================================================================== --- kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/BaseEventTrackingService.java (revision 306520) +++ kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/BaseEventTrackingService.java (working copy) @@ -385,7 +385,7 @@ * @param e * @return */ - private BaseEvent ensureBaseEvent(Event e) + protected BaseEvent ensureBaseEvent(Event e) { BaseEvent event = null; if (e instanceof BaseEvent) @@ -494,7 +494,7 @@ * Event objects are posted to the EventTracking service, and may be listened for. *

*/ - protected class BaseEvent implements Event, Serializable + protected class BaseEvent implements Event { /** * Be a good Serializable citizen @@ -526,7 +526,7 @@ protected int m_priority = NotificationService.NOTI_OPTIONAL; /** Event creation time. */ - protected Time m_time = null; + protected Date m_time = null; /** * Access the event id string @@ -701,7 +701,7 @@ { this(event, resource, context, modify, priority); m_seq = seq; - m_time = timeService().newTime(eventDate.getTime()); + m_time = eventDate; } /** Index: kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java =================================================================== --- kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (revision 306520) +++ kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (working copy) @@ -24,6 +24,7 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; @@ -31,6 +32,10 @@ import java.util.Map; import java.util.Vector; +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.Ehcache; +import net.sf.ehcache.Element; + import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,6 +45,8 @@ import org.sakaiproject.db.api.SqlService; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.NotificationService; +import org.sakaiproject.event.api.SimpleEvent; +import org.sakaiproject.memory.api.MemoryService; /** *

@@ -74,6 +81,21 @@ /** Queue of events to write if we are batching. */ protected Collection m_eventQueue = null; + /** The underlying cache manager; injected */ + protected CacheManager cacheManager; + public void setCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + /** create the event cache */ + private Ehcache eventCache; + + /** is caching enabled? */ + private boolean cachingEnabled; + + /** default events cache name */ + public static final String CLUSTER_EVENTS_CACHE_NAME = "events_cluster_cache"; + /************************************************************************************************************************************************* * Dependencies ************************************************************************************************************************************************/ @@ -88,6 +110,7 @@ */ protected abstract ServerConfigurationService serverConfigurationService(); + protected MemoryService memoryService; /************************************************************************************************************************************************* * Configuration ************************************************************************************************************************************************/ @@ -236,6 +259,8 @@ this.post(this.newEvent("server.start", serverConfigurationService().getString("version.sakai", "unknown") + "/" + serverConfigurationService().getString("version.service", "unknown"), false)); + // initialize the caching server, if enabled + initCacheServer(); } catch (Exception t) { @@ -296,7 +321,7 @@ protected void postEvent(Event event) { // mark the event time - ((BaseEvent) event).m_time = timeService().newTime(); + ((BaseEvent) event).m_time = new Date(); // notify locally generated events immediately - // they will not be process again when read back from the database @@ -342,13 +367,22 @@ Object fields[] = new Object[6]; bindValues(event, fields); - // process the insert - boolean ok = sqlService().dbWrite(conn, statement, fields); - if (!ok) - { - M_log.warn(this + ".writeEvent(): dbWrite failed: session: " + fields[3] + " event: " + event.toString()); - } - } + // process the insert + if (cachingEnabled) { + // if caching is enabled, get the last inserted id + Long eventId = sqlService().dbInsert(conn, statement, fields, "EVENT_ID"); + if (eventId != null) { + // write event to cache + writeEventToCluster(event, eventId); + } + } else { + boolean ok = sqlService().dbWrite(conn, statement, fields); + if (!ok) { + M_log.warn(this + ".writeEvent(): dbWrite failed: session: " + + fields[3] + " event: " + event.toString()); + } + } + } /** * Write a batch of events to the db @@ -373,9 +407,9 @@ // Note: investigate batch writing via the jdbc driver: make sure we can still use prepared statements (check out host arrays, too) // -ggolden - // common preparation for each insert - String statement = insertStatement(); - Object fields[] = new Object[6]; + // common preparation for each insert + String statement = insertStatement(); + Object fields[] = new Object[6]; // write all events for (Iterator i = events.iterator(); i.hasNext();) @@ -383,16 +417,31 @@ Event event = (Event) i.next(); bindValues(event, fields); - // process the insert - boolean ok = sqlService().dbWrite(conn, statement, fields); - if (!ok) - { - M_log.warn(this + ".writeBatchEvents(): dbWrite failed: session: " + fields[3] + " event: " + event.toString()); - } - } + // process the insert + if (cachingEnabled) { + conn = sqlService().borrowConnection(); + if (conn.getAutoCommit()) { + conn.setAutoCommit(false); + } + Long eventId = sqlService().dbInsert(conn, statement, fields, "EVENT_ID"); + if (eventId != null) { + // write event to cache + writeEventToCluster(event, eventId); + } + } else { + boolean ok = sqlService().dbWrite(conn, statement, fields); + if (!ok) { + M_log.warn(this + + ".writeBatchEvents(): dbWrite failed: session: " + + fields[3] + " event: " + event.toString()); + } + } + } // commit - conn.commit(); + if (!conn.isClosed()) { + conn.commit(); + } } catch (Exception e) { @@ -407,7 +456,7 @@ M_log.warn(this + ".writeBatchEvents, while rolling back: " + ee); } } - M_log.warn(this + ".writeBatchEvents: " + e); + M_log.warn(this + ".writeBatchEvents: " + e, e); } finally { @@ -415,7 +464,7 @@ { try { - if (conn.getAutoCommit() != wasCommit) + if (!conn.isClosed() && conn.getAutoCommit() != wasCommit) { conn.setAutoCommit(wasCommit); } @@ -422,7 +471,7 @@ } catch (Exception e) { - M_log.warn(this + ".writeBatchEvents, while setting auto commit: " + e); + M_log.warn(this + ".writeBatchEvents, while setting auto commit: " + e, e); } sqlService().returnConnection(conn); } @@ -553,81 +602,105 @@ Object[] fields = new Object[1]; fields[0] = Long.valueOf(m_lastEventSeq); - List events = sqlService().dbRead(statement, fields, new SqlReader() - { - public Object readSqlResultRecord(ResultSet result) - { - try - { - // read the Event - long id = result.getLong(1); - Date date = new Date(result.getTimestamp(2, sqlService().getCal()).getTime()); - String function = result.getString(3); - String ref = result.getString(4); - String session = result.getString(5); - String code = result.getString(6); - String context = result.getString(7); - String eventSessionServerId = result.getString(8); // may be null + List events = new ArrayList(); + if (cachingEnabled) { + // set to last event id processed + 1 since we've already processed the last event id + long beginEventId = m_lastEventSeq + 1; + // set m_lastEventSeq to latest key value in event cache + initLastEventIdInEventCache(); + // only process events if there are new ones + if (m_lastEventSeq >= beginEventId) { + for (long i = beginEventId; i <= m_lastEventSeq; i++) { + Element eventElement = eventCache.getQuiet(i); + if (eventElement != null) { + SimpleEvent event = (SimpleEvent) eventElement.getObjectValue(); - // for each one (really, for the last one), update the last event seen seq number - if (id > m_lastEventSeq) - { - m_lastEventSeq = id; - } + if (event != null) { + boolean nonSessionEvent = (event.getServerId() == null || StringUtils.startsWith(event.getSessionId(), "~")); + String userId = null; + boolean skipIt = false; - boolean nonSessionEvent = (eventSessionServerId == null || session.startsWith("~")); - String userId = null; - boolean skipIt = false; + if (nonSessionEvent) { + String[] parts = StringUtils.split(event.getSessionId(), "~"); + if (parts.length > 1) { + userId = parts[1]; + } - if (nonSessionEvent) - { - String[] parts = StringUtils.split(session, "~"); - if (parts.length > 1) { - userId = parts[1]; - } + // we skip this event if it came from our server + if (parts.length > 0) { + skipIt = serverId.equals(parts[0]); + } - // we skip this event if it came from our server - if (parts.length > 0) { - skipIt = serverId.equals(parts[0]); - } - } + event.setUserId(userId); + } else { + skipIt = serverInstance.equals(event.getServerId()); + event.setSessionId(event.getSessionId()); + } - // for session events, if the event is from this server instance, - // we have already processed it and can skip it here. - else - { - skipIt = serverInstance.equals(eventSessionServerId); - } + // add event to list, only if it is not a local server event + if (!skipIt) { + events.add(event); + } + } + } + } + } + } else { + events = sqlService().dbRead(statement, fields, new SqlReader() { + public Object readSqlResultRecord(ResultSet result) { + try { + Long id = result.getLong(1); + Date date = new Date(result.getTimestamp(2, sqlService().getCal()).getTime()); + String function = result.getString(3); + String ref = result.getString(4); + String session = result.getString(5); + String code = result.getString(6); + String context = result.getString(7); + String eventSessionServerId = result.getString(8); // may be null - if (skipIt) - { - return null; - } + if (id > m_lastEventSeq) { + m_lastEventSeq = id; + } - // Note: events from outside the server don't need notification info, since notification is processed only on internal - // events -ggolden - BaseEvent event = new BaseEvent(id, function, ref, context, "m".equals(code), NotificationService.NOTI_NONE, date); - if (nonSessionEvent) - { - event.setUserId(userId); - } - else - { - event.setSessionId(session); - } + boolean nonSessionEvent = (eventSessionServerId == null || session.startsWith("~")); + String userId = null; + boolean skipIt = false; - return event; - } - catch (SQLException ignore) - { - return null; - } - } - }); + if (nonSessionEvent) { + String[] parts = StringUtils.split(session, "~"); + if (parts.length > 1) { + userId = parts[1]; + } + // we skip this event if it came from our server + if (parts.length > 0) { + skipIt = serverId.equals(parts[0]); + } + } else { + skipIt = serverInstance.equals(eventSessionServerId); + } + + if (skipIt) { + return null; + } + + // Note: events from outside the server don't need notification info, since notification is processed only on internal + // events -ggolden + BaseEvent event = new BaseEvent(id, function, ref, context, "m".equals(code), NotificationService.NOTI_NONE, date); + if (nonSessionEvent) { + event.setUserId(userId); + } else { + event.setSessionId(session); + } + return event; + } catch (Exception ignore) { + return null; + } + } + }); + } // for each new event found, notify observers - for (int i = 0; i < events.size(); i++) - { + for (int i = 0; i < events.size(); i++) { Event event = (Event) events.get(i); notifyObservers(event, false); } @@ -673,4 +746,64 @@ if (M_log.isDebugEnabled()) M_log.debug(this + " Starting (after) Event #: " + m_lastEventSeq); } + + /** + * Initializes the Terracotta cache, if enabled + */ + private void initCacheServer() { + String cacheName = "org.sakaiproject.event.impl.ClusterEventTracking.eventsCache"; + cachingEnabled = cacheManager.cacheExists(cacheName) + && serverConfigurationService().getBoolean("cluster.cache.enabled", false); + if (cachingEnabled) { + eventCache = cacheManager.getCache(cacheName); + if (M_log.isDebugEnabled()) { + M_log.debug("cache manager: "+cacheManager.toString()); + } + } + } + + /** + * Finds the last event ID inserted into the event cache + */ + private void initLastEventIdInEventCache() { + if (eventCache != null) { + List ids = eventCache.getKeys(); + if (ids != null) { + for (Object id : ids) { + Long i = (Long) id; + if (i > m_lastEventSeq) { + m_lastEventSeq = i; + } + } + } + } + } + + /** + * Writes an event to cache, if enabled + * + * @param event the event object + * @param eventId the id of the event object + */ + private void writeEventToCluster(Event event, Long eventId) { + if (cachingEnabled) { + if (eventCache != null) { + // store event as an element + BaseEvent baseEvent = ensureBaseEvent(event); + SimpleEvent simpleEvent = new SimpleEvent((Event) baseEvent, serverConfigurationService().getServerIdInstance()); + Element eventElement = new Element(eventId, simpleEvent); + // add item to cache store + eventCache.put(eventElement); + } else { + if (M_log.isDebugEnabled()) { + M_log.info("Cannot store event to cache, event store not initialized."); + } + } + } else { + if (M_log.isDebugEnabled()) { + M_log.info("Cluster caching not enabled."); + } + } + } + } Index: kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/SessionServiceAdaptorTest.java =================================================================== --- kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/SessionServiceAdaptorTest.java (revision 306520) +++ kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/SessionServiceAdaptorTest.java (working copy) @@ -37,87 +37,129 @@ * SessionServiceAdaptorTest extends the db alias service providing the dependency injectors for testing. * *

*/ -public class SessionServiceAdaptorTest extends UsageSessionServiceAdaptor -{ - /** - * @return the TimeService collaborator. - */ - protected TimeService timeService() - { - return null; - } +@SuppressWarnings("unchecked") +public class SessionServiceAdaptorTest extends UsageSessionServiceAdaptor { - /** Dependency: SqlService. */ - /** - * @return the SqlService collaborator. - */ - protected SqlService sqlService() - { - return null; - } + TimeService timeService; + SqlService sqlService; + ServerConfigurationService serverConfigurationService; + ThreadLocalManager threadLocalManager; + SessionManager sessionManager; + IdManager idManager; + EventTrackingService eventTrackingService; + AuthzGroupService authzGroupService; + UserDirectoryService userDirectoryService; + MemoryService memoryService; - /** - * @return the ServerConfigurationService collaborator. - */ - protected ServerConfigurationService serverConfigurationService() - { - return null; - } + public void setTimeService(TimeService timeService) { + this.timeService = timeService; + } - /** - * @return the ThreadLocalManager collaborator. - */ - protected ThreadLocalManager threadLocalManager() - { - return null; - } + public void setSqlService(SqlService sqlService) { + this.sqlService = sqlService; + } - /** - * @return the SessionManager collaborator. - */ - protected SessionManager sessionManager() - { - return null; - } + public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) { + this.serverConfigurationService = serverConfigurationService; + } - /** - * @return the IdManager collaborator. - */ - protected IdManager idManager() - { - return null; - } + public void setThreadLocalManager(ThreadLocalManager threadLocalManager) { + this.threadLocalManager = threadLocalManager; + } - /** - * @return the EventTrackingService collaborator. - */ - protected EventTrackingService eventTrackingService() - { - return null; - } + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } - /** - * @return the AuthzGroupService collaborator. - */ - protected AuthzGroupService authzGroupService() - { - return null; - } + public void setIdManager(IdManager idManager) { + this.idManager = idManager; + } - /** - * @return the UserDirectoryService collaborator. - */ - protected UserDirectoryService userDirectoryService() - { - return null; - } - - /** - * @return the MemoryService collaborator. - */ - protected MemoryService memoryService() - { - return null; - } + public void setEventTrackingService(EventTrackingService eventTrackingService) { + this.eventTrackingService = eventTrackingService; + } + public void setAuthzGroupService(AuthzGroupService authzGroupService) { + this.authzGroupService = authzGroupService; + } + + public void setUserDirectoryService(UserDirectoryService userDirectoryService) { + this.userDirectoryService = userDirectoryService; + } + + public void setMemoryService(MemoryService memoryService) { + this.memoryService = memoryService; + } + + /** + * @return the TimeService collaborator. + */ + protected TimeService timeService() { + return timeService; + } + + /** Dependency: SqlService. */ + /** + * @return the SqlService collaborator. + */ + protected SqlService sqlService() { + return sqlService; + } + + /** + * @return the ServerConfigurationService collaborator. + */ + protected ServerConfigurationService serverConfigurationService() { + return serverConfigurationService; + } + + /** + * @return the ThreadLocalManager collaborator. + */ + protected ThreadLocalManager threadLocalManager() { + return threadLocalManager; + } + + /** + * @return the SessionManager collaborator. + */ + protected SessionManager sessionManager() { + return sessionManager; + } + + /** + * @return the IdManager collaborator. + */ + protected IdManager idManager() { + return idManager; + } + + /** + * @return the EventTrackingService collaborator. + */ + protected EventTrackingService eventTrackingService() { + return eventTrackingService; + } + + /** + * @return the AuthzGroupService collaborator. + */ + protected AuthzGroupService authzGroupService() { + return authzGroupService; + } + + /** + * @return the UserDirectoryService collaborator. + */ + protected UserDirectoryService userDirectoryService() { + return userDirectoryService; + } + + /** + * @return the MemoryService collaborator. + */ + protected MemoryService memoryService() { + return memoryService; + } + } Index: config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties =================================================================== --- config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (revision 306478) +++ config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (working copy) @@ -1024,6 +1024,37 @@ # DEFAULT: false (do not surpress) # suppressCMRefresh=true +# Events and Role Caching - uses a distributed cache to propagate events and roles, rather than reading from the database +# Data is still persisted to the database +# Enable distributed caching +# DEFAULT: false +#cluster.cache.enabled=false + +# The URLs of the distributed cache servers +#cluster.cache.server.urls.count=2 +#cluster.cache.server.urls.1={CACHE_SERVER_URL_1}:9510 +#cluster.cache.server.urls.2={CACHE_SERVER_URL_2}:9511 + +# The caches that will be using the distributed cache. +# The only ones allowed are eventsCache and realmRoleGroupCache +#cluster.cache.names.count=2 +#cluster.cache.names.1=org.sakaiproject.event.impl.ClusterEventTracking.eventsCache +#cluster.cache.names.2=org.sakaiproject.authz.impl.DbAuthzGroupService.realmRoleGroupCache + +# Any Event or Role properties below that are not set will have a reasonable default value + +# Event caching properties +#org.sakaiproject.event.impl.ClusterEventTracking.eventCache.cache.maxEntriesLocalHeap=10000000 +#org.sakaiproject.event.impl.ClusterEventTracking.eventCache.cache.timeToIdle=120 +#org.sakaiproject.event.impl.ClusterEventTracking.eventCache.cache.timeToLive=120 +#org.sakaiproject.event.impl.ClusterEventTracking.eventCache.cache.maxEntriesLocalDisk=10000000 + +# Role and Group caching properties +#org.sakaiproject.authz.impl.DbAuthzGroupService.realmRoleGroupCache.cache.maxEntriesLocalHeap=10000000 +#org.sakaiproject.authz.impl.DbAuthzGroupService.realmRoleGroupCache.cache.timeToIdle=0 +#org.sakaiproject.authz.impl.DbAuthzGroupService.realmRoleGroupCache.cache.timeToLive=2400 +#org.sakaiproject.authz.impl.DbAuthzGroupService.realmRoleGroupCache.cache.maxEntriesLocalDisk=10000000 + # ######################################################################## # SESSION MANAGEMENT # ########################################################################