View Javadoc

1   package com.atlassian.user.impl.cache;
2   
3   import com.atlassian.cache.Cache;
4   import com.atlassian.cache.CacheFactory;
5   import com.atlassian.user.Entity;
6   import com.atlassian.user.EntityException;
7   import com.atlassian.user.User;
8   import com.atlassian.user.UserManager;
9   import com.atlassian.user.impl.DefaultUser;
10  import com.atlassian.user.repository.RepositoryIdentifier;
11  import com.atlassian.user.search.page.Pager;
12  import org.apache.log4j.Logger;
13  
14  public class CachingUserManager implements UserManager
15  {
16      private static final Logger log = (Logger) Logger.getLogger(CachingUserManager.class);
17  
18      private final UserManager underlyingUserManager;
19      private final CacheFactory cacheFactory;
20  
21      private String userCacheName = null;
22      private String userROCacheName = null;
23      private String repositoryCacheName = null;
24  
25      private static final String CACHE_SUFFIX_USERS = "users";
26      private static final String CACHE_SUFFIX_USERS_RO = "users_ro";
27      private static final String CACHE_SUFFIX_REPOSITORIES = "repository";
28  
29      protected static User NULL_USER = new DefaultUser()
30      {
31          public String toString()
32          {
33              return "NULL USER";
34          }
35      };
36  
37      public CachingUserManager(UserManager underlyingUserManager, CacheFactory cacheFactory)
38      {
39          this.underlyingUserManager = underlyingUserManager;
40          this.cacheFactory = cacheFactory;
41      }
42  
43      public Pager<User> getUsers() throws EntityException
44      {
45          return underlyingUserManager.getUsers();
46      }
47  
48      public Pager<String> getUserNames() throws EntityException
49      {
50          return underlyingUserManager.getUserNames();
51      }
52  
53      /**
54       * Caches users retrieved.<br>
55       * Will also cache the fact that a user could not be found (so that we don't incur the expense of another search when have previously determined that a user doesn't exist)
56       * Hence this method will also cache null results.
57       *
58       * @return - an {@link com.atlassian.user.User} if one could be found, otherwise null.
59       * @throws com.atlassian.user.EntityException
60       *          - representing the exception which prohibited looking for or
61       *          retrieving the user.
62       */
63      public User getUser(String username) throws EntityException
64      {
65          User cachedUser = (User) getUserCache().get(username);
66          if (cachedUser != null)
67          {
68              return NULL_USER.equals(cachedUser) ? null : cachedUser;
69          }
70          else
71          {
72              User user = underlyingUserManager.getUser(username);
73              cacheUser(username, user);
74              return user;
75          }
76      }
77  
78      private void cacheUser(String username, User user)
79      {
80          getUserCache().put(username, user == null ? NULL_USER : user);
81      }
82  
83      private void cacheRepository(String username, RepositoryIdentifier repository)
84      {
85          getRepositoryCache().put(username, repository);
86      }
87  
88      private void cacheUserROFlag(User user, boolean ro)
89      {
90          getUserROFlagCache().put(user.getName(), ro);
91      }
92  
93      private Cache getUserCache()
94      {
95          synchronized(this)
96          {
97              if (userCacheName == null)
98                  userCacheName = getCacheKey(CACHE_SUFFIX_USERS);
99          }
100         return cacheFactory.getCache(userCacheName);
101     }
102 
103     private Cache getUserROFlagCache()
104     {
105         synchronized(this)
106         {
107             if (userROCacheName == null)
108                 userROCacheName = getCacheKey(CACHE_SUFFIX_USERS_RO);
109         }
110         return cacheFactory.getCache(userROCacheName);
111     }
112 
113     private Cache getRepositoryCache()
114     {
115         synchronized(this)
116         {
117             if (repositoryCacheName == null)
118                 repositoryCacheName = getCacheKey(CACHE_SUFFIX_REPOSITORIES);
119         }
120         return cacheFactory.getCache(repositoryCacheName);
121     }
122 
123     public User createUser(String username) throws EntityException
124     {
125         User user = underlyingUserManager.createUser(username);
126 
127         if (user != null)
128             cacheUser(user.getName(), user);
129 
130         return user;
131     }
132 
133     /**
134      * Encrypts the plain password, sets it on the user, and saves the user.
135      */
136     public void alterPassword(User user, String plainTextPass) throws EntityException
137     {
138         underlyingUserManager.alterPassword(user, plainTextPass);
139         if (user != null)
140             cacheUser(user.getName(), underlyingUserManager.getUser(user.getName()));
141     }
142 
143     public void saveUser(User user) throws EntityException
144     {
145         underlyingUserManager.saveUser(user);
146 
147         if (user != null)
148             cacheUser(user.getName(), user);
149     }
150 
151     /**
152      * Removes the specified group, if it is present.
153      */
154     public void removeUser(User user) throws EntityException
155     {
156         if (log.isDebugEnabled())
157             log.debug("removing user: " + user.getName());
158         underlyingUserManager.removeUser(user);
159         if (log.isDebugEnabled())
160             log.debug("user " + user.getName() + " removed from underlying user manager " + underlyingUserManager.getIdentifier().getName());
161 
162         try
163         {
164             if (log.isDebugEnabled())
165                 log.debug("removing user from cache: " + user.getName());
166             removeUserFromCache(user);
167             if (log.isDebugEnabled())
168             {
169                 log.debug("removed user from cache: " + user.getName());
170                 // Lets be paranoid
171                 // TODO delete it
172                 if (getUserCache().get(user.getName()) != null)
173                 {
174                     log.error("WTF???");    
175                 }
176             }
177         }
178         catch (Exception e)
179         {
180             throw new EntityException("User removed in underlying repository but could not remove from cache");
181         }
182     }
183 
184     private void removeUserFromCache(User user)
185     {
186         if (user != null)
187             getUserCache().remove(user.getName());
188     }
189 
190     /**
191      * @return true indicates that information on the user object cannot be altered in the storage system
192      *         (see {@link com.atlassian.user.repository.RepositoryIdentifier}),
193      *         false indicates that the storage system will save changes or that this {@link com.atlassian.user.UserManager} does not
194      *         know about the {@link com.atlassian.user.User}.
195      *
196      */
197     public boolean isReadOnly(User user) throws EntityException
198     {
199         Boolean cachedROFlag = (Boolean) getUserROFlagCache().get(user.getName());
200 
201         if (cachedROFlag == null)
202         {
203             boolean ro = underlyingUserManager.isReadOnly(user);
204             cacheUserROFlag(user, ro);
205             return ro;
206         }
207         else
208         {
209             return cachedROFlag;
210         }
211     }
212 
213     public RepositoryIdentifier getIdentifier()
214     {
215         return underlyingUserManager.getIdentifier();
216     }
217 
218     /**
219      * @return the {@link com.atlassian.user.repository.RepositoryIdentifier} in which the entity is stored, otherwise null.
220      */
221     public RepositoryIdentifier getRepository(Entity entity) throws EntityException
222     {
223         RepositoryIdentifier cachedRepository = (RepositoryIdentifier) getRepositoryCache().get(entity.getName());
224         if (cachedRepository != null)
225             return cachedRepository;
226 
227         RepositoryIdentifier repository = underlyingUserManager.getRepository(entity);
228         cacheRepository(entity.getName(), repository);
229         return repository;
230     }
231 
232     /**
233      * Used to detemine whether an entity can be created (eg, can call {@link com.atlassian.user.UserManager#createUser(String)} or
234      * {@link com.atlassian.user.GroupManager#createGroup(String)}
235      *
236      * @return true to indicate that {@link com.atlassian.user.Entity} objects can be created by this manager, or false to indicate
237      *         not.
238      */
239     public boolean isCreative()
240     {
241         return underlyingUserManager.isCreative();
242     }
243 
244     /**
245      * Generates a unique cache key. This cache key should not be shared by other instances of CachingUserManager that delegate to different underlying user managers.
246      * @return cache key
247      */
248     private String getCacheKey(String cacheName)
249     {
250         String className = underlyingUserManager.getClass().getName();
251         String repositoryKey = underlyingUserManager.getIdentifier().getKey(); // use the repository key to compose the cache key, so we can have a cache per (repository + userManager) combination.
252         return className + "." + repositoryKey + "." + cacheName;
253     }
254 
255 }