org.activiti.explorer.cache.TrieBasedUserCache.java Source code

Java tutorial

Introduction

Here is the source code for org.activiti.explorer.cache.TrieBasedUserCache.java

Source

/* 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.explorer.cache;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.activiti.engine.IdentityService;
import org.activiti.engine.identity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Simple cache of user information, to avoid hitting the database too often for 
 * information that doesn't change much over time.
 * 
 * Based on a Trie datastructure (http://en.wikipedia.org/wiki/Trie), see {@link RadixTree},
 * for fast 'telephonebook'-like retrieval based on the first and last name of the users.
 * Note that we are using the Trie such that we can have multiple results for a given key, by
 * giving each key a list of matching values:
 * eg. key='kermit' has a list of values {Kermit The Frog, Kermit The Evil Overlord, ...} 
 * 
 * TODO: In a clustered/cloud environment, this cache must be refreshed each xx minutes,
 * in case updates have been done on other machines. Alternatively, a solution
 * such as memcached could replace this implementation later on.
 * 
 * @author Joram Barrez
 */
@Component
public class TrieBasedUserCache implements UserCache {

    private static final Logger LOGGER = Logger.getLogger(TrieBasedUserCache.class.getName());

    protected IdentityService identityService;
    protected RadixTree<List<User>> userTrie = new RadixTreeImpl<List<User>>();
    protected Map<String, List<String>> keyCache = new HashMap<String, List<String>>();
    protected Map<String, User> userCache = new HashMap<String, User>();

    public void refresh() {
        userTrie = new RadixTreeImpl<List<User>>();
        loadUsers();
    }

    public synchronized void loadUsers() {
        long nrOfUsers = identityService.createUserQuery().count();
        long usersAdded = 0;

        userTrie = new RadixTreeImpl<List<User>>();
        userCache = new HashMap<String, User>();
        keyCache = new HashMap<String, List<String>>();

        while (usersAdded < nrOfUsers) {

            if (LOGGER.isLoggable(Level.INFO)) {
                LOGGER.info("Caching users " + usersAdded + " to " + (usersAdded + 25));
            }

            List<User> users = identityService.createUserQuery().listPage((int) usersAdded, 25);
            for (User user : users) {
                addTrieItem(user);
                addUserCacheItem(user);

                usersAdded++;
            }
        }
    }

    protected void addTrieItem(User user) {
        for (String key : getKeys(user)) {
            addTrieCacheItem(key, user);
        }
    }

    protected String[] getKeys(User user) {
        String fullname = "";
        if (user.getFirstName() != null) {
            fullname += user.getFirstName();
        }
        if (user.getLastName() != null) {
            fullname += " " + user.getLastName();
        }

        return fullname.split(" ");
    }

    protected void addTrieCacheItem(String key, User user) {
        key = key.toLowerCase();

        // Trie update
        List<User> value = null;
        if (!userTrie.contains(key)) {
            value = new ArrayList<User>();
        } else {
            value = userTrie.find(key);
        }

        value.add(user);
        userTrie.delete(key);
        userTrie.insert(key, value);

        // Key map update
        if (!keyCache.containsKey(user.getId())) {
            keyCache.put(user.getId(), new ArrayList<String>());
        }
        keyCache.get(user.getId()).add(key);
    }

    protected void addUserCacheItem(User user) {
        userCache.put(user.getId(), user);
    }

    public User findUser(String userId) {
        if (userCache.isEmpty()) {
            loadUsers();
        }
        return userCache.get(userId);
    }

    public List<User> findMatchingUsers(String prefix) {

        if (userTrie.getSize() == 0) {
            refresh();
        }

        List<User> returnValue = new ArrayList<User>();
        List<List<User>> results = userTrie.searchPrefix(prefix.toLowerCase(), 100); // 100 should be enough for any name
        for (List<User> result : results) {
            for (User userDetail : result) {
                returnValue.add(userDetail);
            }
        }
        return returnValue;
    }

    public void notifyUserDataChanged(String userId) {
        User newData = identityService.createUserQuery().userId(userId).singleResult();

        // Update user trie: first remove old values
        if (keyCache.containsKey(userId)) {
            for (String key : keyCache.get(userId)) {
                List<User> users = userTrie.find(key);
                if (users != null && !users.isEmpty()) {
                    Iterator<User> userIterator = users.iterator();
                    while (userIterator.hasNext()) {
                        User next = userIterator.next();
                        if (next.getId().equals(userId)) {
                            userIterator.remove();
                        }
                    }
                }
            }
        }

        // Update key cache
        keyCache.remove(userId);

        if (newData != null) {
            // Update user trie: add new value
            addTrieItem(newData);

            // Update user cache
            userCache.put(newData.getId(), newData);
        }
    }

    @Autowired
    public void setIdentityService(IdentityService identityService) {
        this.identityService = identityService;
        loadUsers();
    }

}