/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/user/impl/PrecachingDbUserService.java $ * $Id: PrecachingDbUserService.java 125281 2013-05-31 03:42:46Z nbotimer@unicon.net $ *********************************************************************************** * * Copyright (c) 2005, 2006, 2008, 2009, 2010 Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.user.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.sakaiproject.user.api.UserNotDefinedException; /** * This will refresh the user cache based on configuration options, * it handles the refresh by force clearing entries and then simply looking up all * users who are actively enrolled * * this code is based on UserCacheRefresher.java by Aaron Zeckoski. * * Aaron's code extended JdbcDaoSupport.java and obtained the cache from spring component definitions. * That didn't work OOTB. * I simplied the spring definitions by extending DbUserService.java instead, * and so getting access to the needed cache in the singleton object instance. * Java single inheritance requires a separate class DirectDbAccess.java to extend JdbcDaoSupport.java. * * I also added a second timer and changed the scheduling so that the 2 timers provide both * an immediate boot-time pre-caching of users and followup pre-cache runs daily. * * created in UVa SAK-1382 (wdn5e 2010.09.22) * * @author Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) */ public abstract class PrecachingDbUserService extends DbUserService { private static Log log = LogFactory.getLog(PrecachingDbUserService.class); boolean logUsersRemoved = false; boolean logUsersNotRemoved = false; boolean logUsersAccessed = false; boolean logUsersNotAccessed = true; /** * Query to retrieve all distinct userIds from all sites */ protected String siteUserIdsQuery = "SELECT distinct(USER_ID) FROM SAKAI_SITE_USER where PERMISSION = 1 order by USER_ID"; public Timer bootTimer = new Timer("boot precache users", true); public Timer dailyTimer = new Timer("daily precache users", true); /** * followup scheduled run, repeated daily at a given time */ protected TimerTask scheduledTask; /** * 2nd task for an immediate initial run * this runs immediately (in a few minutes) and doesn't rerun */ protected TimerTask onetimeTask; private DirectDbAccess directDbAccess; public void setDirectDbAccess (DirectDbAccess directDbAccess) { this.directDbAccess = directDbAccess; } /** UVa SAK-1382: replace Aaron's init() code to schedule one task to run at the same time each day * add a 2nd task "onetimeTask" to run immediately." */ public void init() { super.init(); if (log.isDebugEnabled()) { log.debug("init(): (grand-super) BaseUserDirectoryService includes this general cache just created in its code, m_callCache==" + m_callCache); log.debug("init(): (super) DbUserService includes this eid/id map, wired in user-components.xml, cache==" + cache); } // LOAD the various sakai config options Boolean runOnStartup = serverConfigurationService().getBoolean("precache.users.run.startup", false); Boolean runDaily = serverConfigurationService().getBoolean("precache.users.run.daily", false); String cacheTimeString = serverConfigurationService().getString("precache.users.refresh.time", "04:00"); this.siteUserIdsQuery = serverConfigurationService().getString("precache.users.userlist.query", this.siteUserIdsQuery); this.logUsersRemoved = serverConfigurationService().getBoolean("precache.users.log.usersRemoved", this.logUsersRemoved); this.logUsersNotRemoved = serverConfigurationService().getBoolean("precache.users.log.usersNotRemoved", this.logUsersNotRemoved); this.logUsersAccessed = serverConfigurationService().getBoolean("precache.users.log.usersAccessed", this.logUsersAccessed); this.logUsersNotAccessed = serverConfigurationService().getBoolean("precache.users.log.usersNotAccessed", this.logUsersNotAccessed); Calendar cal = Calendar.getInstance(); if (runOnStartup) { log.info("init() scheduling user precache for startup run"); // set up onetime task to run after short delay cal.setTime(new Date()); cal.add(Calendar.MINUTE, 5); Date onetimeTaskStart = cal.getTime(); onetimeTask = new UserCacheTimerTask(); bootTimer.schedule(onetimeTask, onetimeTaskStart); log.info("User precache refresh onetime task scheduled to run in 5 minutes without repetition."); } else { log.info("User precache not scheduled for startup run"); } if (runDaily) { // set up recurring task cal.setTime(new Date()); cal.add(Calendar.DATE, 1); // start tomorrow long recurringTaskPeriod = 24l * 60l * 60l * 1000l; log.info("User precache will schedule recurring task every 24 hours, beginning tomorrow"); try { String[] parts = cacheTimeString.trim().split(":"); Integer hour = new Integer(parts[0]); if (hour < 12) { cal.set(Calendar.AM_PM, Calendar.AM); } else { cal.set(Calendar.AM_PM, Calendar.PM); } cal.set(Calendar.HOUR, hour); cal.set(Calendar.MINUTE, new Integer(parts[1]) ); Date recurringTaskStart = cal.getTime(); scheduledTask = new UserCacheTimerTask(); dailyTimer.scheduleAtFixedRate(scheduledTask, recurringTaskStart, recurringTaskPeriod); log.info("User precache scheduled for daily run at " + cacheTimeString); } catch (RuntimeException e) { log.error("User precache: Didn't schedule user cache refresh: Bad config?, it should be like: 'precache.users.refresh.time = 04:00' : " + e.getMessage(), e); } } else { log.info("User precache not scheduled for daily run"); } } public void doCacheRefresh(String siteUserIdsQuery) { if (log.isDebugEnabled()) { log.debug("USER PRECACHE BEGINNING"); log.debug("doCacheRefresh(): using siteUserIdsQuery==" + siteUserIdsQuery); } //@SuppressWarnings("unchecked") List<Map<String, Object>> results = directDbAccess.getJdbcTemplate().queryForList(siteUserIdsQuery); List<String> userIds = new ArrayList<String>(); for (Map<String, Object> row : results) { Object userId = row.get("USER_ID"); if (userId != null) { userIds.add(userId.toString()); } } if (userIds.isEmpty()) { log.warn("doCacheRefresh(): No userIds found as participants while trying to refresh all cache users, cannot refresh"); } else { log.info("doCacheRefresh(): Found " + userIds.size() + " users to refresh, initiating user cache refreshing..."); int removedCount = 0; int notRemovedCount = 0; int accessedCount = 0; int notAccessedCount = 0; List<String> removedUsers = new ArrayList<String>(); List<String> notRemovedUsers = new ArrayList<String>(); List<String> accessedUsers = new ArrayList<String>(); List<String> notAccessedUsers = new ArrayList<String>(); long totalTime = 0; for (String userId : userIds) { // clear existing cache entry String key = makeUserRef(userId); if (log.isDebugEnabled()) { log.debug("doCacheRefresh(): NEW key==[" + key + "] in cache? before removing: " + m_callCache.containsKey(key)); } if (m_callCache.containsKey(key)) { m_callCache.remove(key); removedCount++; if (logUsersRemoved) { removedUsers.add(userId); } } else { notRemovedCount++; if (logUsersNotRemoved) { notRemovedUsers.add(userId); } } if (log.isDebugEnabled()) { log.debug("doCacheRefresh(): NEW key==[" + key + "] in cache? after removing: " + m_callCache.containsKey(key)); } // redo the lookup of this user which will reload the cache try { long before = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("doCacheRefresh(): key==[" + key + "] in cache? before accessing: " + m_callCache.containsKey(key)); } getUser(userId); if (log.isDebugEnabled()) { log.debug("doCacheRefresh(): key==[" + key + "] in cache? after accessing: " + m_callCache.containsKey(key)); } long after = System.currentTimeMillis(); totalTime += after - before; accessedCount++; if (logUsersAccessed) { accessedUsers.add(userId); } } catch (UserNotDefinedException e) { notAccessedCount++; if (logUsersNotAccessed) { notAccessedUsers.add(userId); } } } // now output the results of cache reset in the logs as configured String delimiter = ""; if (logUsersRemoved) { delimiter = ""; StringBuilder removedUserSB = new StringBuilder(); for (String userId : removedUsers) { removedUserSB.append(delimiter); removedUserSB.append(userId); delimiter = ":"; } log.info("doCacheRefresh(): " + removedCount + " entries removed from cache"); log.info("doCacheRefresh(): These users found in cache and so removed: [" + removedUserSB.toString() + "]"); } if (logUsersNotRemoved) { delimiter = ""; StringBuilder notRemovedUserSB = new StringBuilder(); for (String userId : notRemovedUsers) { notRemovedUserSB.append(delimiter); notRemovedUserSB.append(userId); delimiter = ":"; } log.info("doCacheRefresh(): " + notRemovedCount + " entries not found in cache"); log.info("doCacheRefresh(): These users not found in cache and so not removed: [" + notRemovedUserSB.toString() + "]"); } if (logUsersAccessed) { delimiter = ""; StringBuilder accessedUserSB = new StringBuilder(); for (String userId : accessedUsers) { accessedUserSB.append(delimiter); accessedUserSB.append(userId); delimiter = ":"; } log.info("doCacheRefresh(): " + accessedCount + " users accessed and so recached"); log.info("doCacheRefresh(): These users accessed and so newly cached: [" + accessedUserSB.toString() + "]"); } if (logUsersNotAccessed) { delimiter = ""; StringBuilder notAccessedUserSB = new StringBuilder(); for (String userId : notAccessedUsers) { notAccessedUserSB.append(delimiter); notAccessedUserSB.append(userId); delimiter = ":"; } log.info("doCacheRefresh(): " + notAccessedCount + " users not found and so not recached"); log.info("doCacheRefresh(): These users not found and so not newly cached: [" + notAccessedUserSB.toString() + "]"); } if (log.isInfoEnabled()) { log.info("doCacheRefresh(): " + totalTime + " milliseconds to cache " + userIds.size() + " users, " + (totalTime / userIds.size()) + " milliseconds per user, while filling the cache"); } } log.info("USER PRECACHE COMPLETED"); } private String makeUserRef(String userId) { return "/user/" + userId; } protected class UserCacheTimerTask extends TimerTask { @Override public void run() { try { doCacheRefresh(siteUserIdsQuery); } catch (Exception e) { log.error("run(): Failure attempting to refresh user cache: " + e.getMessage(), e); } } } }