1   package com.atlassian.user.impl.hibernate;
2   
3   import com.atlassian.user.*;
4   import com.atlassian.user.impl.DuplicateEntityException;
5   import com.atlassian.user.impl.RepositoryException;
6   import com.atlassian.user.impl.hibernate.repository.HibernateRepository;
7   import com.atlassian.user.repository.RepositoryIdentifier;
8   import com.atlassian.user.search.page.DefaultPager;
9   import com.atlassian.user.search.page.Pager;
10  import com.atlassian.user.search.page.PagerFactory;
11  import com.atlassian.user.util.Assert;
12  import net.sf.hibernate.HibernateException;
13  import net.sf.hibernate.ObjectDeletedException;
14  import net.sf.hibernate.Query;
15  import net.sf.hibernate.Session;
16  import org.springframework.dao.DataAccessException;
17  import org.springframework.orm.hibernate.HibernateCallback;
18  import org.springframework.orm.hibernate.HibernateTemplate;
19  import org.springframework.orm.hibernate.SessionFactoryUtils;
20  import org.springframework.orm.hibernate.support.HibernateDaoSupport;
21  
22  import java.util.*;
23  
24  /**
25   * A HibernateGroupManager which handles membership for local and external entities.
26   */
27  public class HibernateGroupManager extends HibernateDaoSupport implements GroupManager
28  {
29      public static final String GROUPNAME_FIELD = "groupname";
30      public static final String GROUPID_FIELD = "groupid";
31      public static final String ENTITYID_FIELD = "entityid";
32      public static final String EXTERNAL_ENTITY_NAME_FIELD = "externalEntityName";
33  
34      private final RepositoryIdentifier identifier;
35      protected final HibernateRepository repository;
36      protected final UserManager userManager;
37      protected final ExternalEntityDAO externalEntityDao;
38  
39      private static final boolean USE_EXPERIMENTAL_MAPPINGS = Boolean.getBoolean("com.atlassian.user.experimentalMapping");
40  
41      public HibernateGroupManager(RepositoryIdentifier identifier, HibernateRepository repository, UserManager userManager, ExternalEntityDAO externalEntityDao)
42      {
43          this.identifier = identifier;
44          this.repository = repository;
45          this.userManager = userManager;
46          setSessionFactory(repository.getSessionFactory());
47          this.externalEntityDao = externalEntityDao;
48      }
49  
50      public Pager<Group> getGroups() throws EntityException
51      {
52          List<Group> result;
53  
54          try
55          {
56              result = getGroupsFromHibernate();
57          }
58          catch (DataAccessException e)
59          {
60              throw new RepositoryException(e);
61          }
62  
63          if (result == null)
64              return DefaultPager.emptyPager();
65  
66          return new DefaultPager<Group>(result);
67      }
68  
69      public List<Group> getWritableGroups()
70      {
71          return getGroupsFromHibernate();
72      }
73  
74      public Pager<Group> getGroups(User user) throws EntityException
75      {
76          Collection<Group> groups = getAllGroupsForUser(user);
77          return new DefaultPager<Group>(groups);
78      }
79  
80      protected Collection<Group> getAllGroupsForUser(User user) throws RepositoryException
81      {
82          Assert.notNull(user, "User must not be null");
83  
84          if (isUserExternal(user))
85              return getGroupsForExternalEntity(getCorrespondingExternalEntity(user));
86  
87          return getGroupsForLocalUser(user);
88      }
89  
90      /**
91       * for the time being, lets define an internal user as a hibernate user. All other impl's of User are external users.
92       */
93      protected boolean isUserExternal(User user)
94      {
95          return !(user instanceof DefaultHibernateUser);
96      }
97  
98      private List<Group> getGroupsForLocalUser(User user) throws RepositoryException
99      {
100         Assert.notNull(user, "User must not be null");
101         Assert.isInstanceOf(DefaultHibernateUser.class, user);
102 
103         if (isUserExternal(user))
104             return Collections.emptyList();
105 
106         return getLocalUserGroupsFromHibernate((DefaultHibernateUser) user);
107     }
108 
109     private List<Group> getGroupsForExternalEntity(final ExternalEntity externalEntity) throws RepositoryException
110     {
111         if (externalEntity == null)
112             throw new IllegalArgumentException("Input (externalEntity) is null.");
113 
114         return getExternalUserGroupsFromHibernate(externalEntity);
115     }
116 
117     /**
118      * @return a {@link Pager} instance which can hold {@link User} <b>and</b> {@link ExternalEntity} objects.
119      */
120     public Pager<String> getMemberNames(Group group) throws EntityException
121     {
122         if (group == null)
123             throw new IllegalArgumentException("Group cannot be null.");
124         if (!isHandledGroup(group))
125             throw new IllegalArgumentException("Group passed to HibernateGroupManager must be of type 'DefaultHibernateGroup'");
126 
127         // We don't try to merge the result returned by getExternalMemberNames(group) and getLocalMemberNames(group)
128         // because it is not possible with hql(not implemented) or native sql through hibernate (bug in hibernate)
129         // When iterating through returned result client will get ordered internal members and then ordered external members.
130         // To order everything together we can use SQL union but HQL does not support it yet
131         // http://opensource.atlassian.com/projects/hibernate/browse/HHH-1050
132         return PagerFactory.getPager(getExternalMemberNames(group), getLocalMemberNames(group));
133     }
134 
135     protected void validateGroup(Group group)
136     {
137         if (group == null)
138             throw new IllegalArgumentException("Input (group) is null.");
139     }
140 
141     public Pager<String> getLocalMemberNames(Group group) throws EntityException
142     {
143         validateGroup(group);
144         return new DefaultPager<String>(getLocalMemberNamesFromHibernate((DefaultHibernateGroup) group));
145     }
146 
147     public Pager<User> getLocalMembers(Group group) throws RepositoryException
148     {
149         if (group == null)
150             throw new IllegalArgumentException("Input (group) is null.");
151         else if (!isHandledGroup(group)) //nothing here for a non-Hibernate group
152             return DefaultPager.emptyPager();
153 
154         DefaultHibernateGroup defGroup = (DefaultHibernateGroup) group;
155         return new DefaultPager<User>(new ArrayList<User>(defGroup.getLocalMembers()));
156     }
157 
158     private boolean isHandledGroup(Group group)
159     {
160         return (group instanceof DefaultHibernateGroup);
161     }
162 
163     public Pager<String> getExternalMemberNames(Group group) throws EntityException
164     {
165         if (group == null)
166             throw new IllegalArgumentException("Input (group) is null.");
167         else if (!isHandledGroup(group))
168             return DefaultPager.emptyPager();
169 
170         final DefaultHibernateGroup defGroup = (DefaultHibernateGroup) group;
171 
172         return new DefaultPager<String>(getExternalMemberNamesFromHibernate(defGroup));
173     }
174 
175     public DefaultHibernateGroup getGroup(Group group) throws EntityException
176     {
177         //for permormance try to fetch a group by id if it is hibernate group
178         if (group instanceof DefaultHibernateGroup)
179         {
180            // let's make sure we load this group in Session
181             try
182             {
183                 return (DefaultHibernateGroup) getSession().get(DefaultHibernateGroup.class, ((DefaultHibernateGroup) group).getId());
184             }
185             catch (ObjectDeletedException e)
186             {
187                 return null;
188             }
189             catch (HibernateException e)
190             {
191                 throw new EntityException(e);
192             }
193         }
194         else
195         {
196             return getGroup(group.getName());
197         }
198 
199     }
200 
201 
202     public DefaultHibernateGroup getGroup(final String groupname) throws EntityException
203     {
204         if (groupname == null)
205             throw new IllegalArgumentException("Input (groupname) is null.");
206 
207         List result;
208         DefaultHibernateGroup foundGroup = null;
209 
210         try
211         {
212             result = getHibernateTemplate().executeFind(new HibernateCallback()
213             {
214                 public Object doInHibernate(Session session) throws HibernateException
215                 {
216                     Query queryObject = session.getNamedQuery("atluser.group_find");
217                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
218 
219                     if (groupname != null)
220                         queryObject.setParameter(GROUPNAME_FIELD, groupname);
221 
222                     return queryObject.list();
223                 }
224             });
225         }
226         catch (DataAccessException e)
227         {
228             throw new RepositoryException(e);
229         }
230 
231         try
232         {
233             foundGroup = (DefaultHibernateGroup) result.get(0);
234         }
235         catch (Exception e)
236         {
237             return foundGroup;
238         }
239 
240         return foundGroup;
241     }
242 
243     public Group createGroup(String groupname) throws EntityException
244     {
245         if (groupname == null)
246             throw new IllegalArgumentException("Input (groupname) is null.");
247 
248         Group group = getGroup(groupname);
249 
250         if (group == null)
251         {
252             group = new DefaultHibernateGroup(groupname);
253             getHibernateTemplate().save(group);
254         }
255         else
256             throw new DuplicateEntityException("Group [" + groupname + "] already exists in this repository (" +
257                     identifier.getName() + ")");
258 
259         return group;
260     }
261 
262     /**
263      * Removes the specified group, if it is present.
264      *
265      * @throws com.atlassian.user.EntityException
266      *          - representing the exception which prohibited removal
267      */
268     public void removeGroup(Group group) throws EntityException
269     {
270         Group groupInSession = getGroupInSession(group);
271 
272         DefaultHibernateGroup dGroup = (DefaultHibernateGroup) groupInSession;
273         dGroup.setExternalMembers(null);
274         dGroup.setLocalMembers(null);
275 
276         getHibernateTemplate().delete(groupInSession);
277     }
278 
279     public void addMembership(Group group, User user) throws EntityException
280     {
281         validateGroupAndUser(group, user);
282 
283         DefaultHibernateGroup dGroup = getGroupInSession(group);
284 
285         if (isUserExternal(user))
286         {
287             addExternalUserMembership(user, dGroup);
288         }
289         else
290         {
291             addLocalUserMembership(user, dGroup);
292         }
293 
294         getHibernateTemplate().saveOrUpdate(dGroup);
295     }
296 
297     private void addLocalUserMembership(User user, DefaultHibernateGroup dGroup)
298     {
299         if (USE_EXPERIMENTAL_MAPPINGS)
300         {
301             DefaultHibernateUser huser = (DefaultHibernateUser) user;
302             huser.getGroups().add(dGroup);
303             getHibernateTemplate().saveOrUpdate(user);
304         }
305         else
306         {
307             if (dGroup.getLocalMembers() == null)
308                 dGroup.setLocalMembers(new HashSet<User>());
309 
310             dGroup.getLocalMembers().add(user);
311         }
312     }
313 
314     private void addExternalUserMembership(User user, DefaultHibernateGroup dGroup)
315             throws RepositoryException
316     {
317         if (dGroup.getExternalMembers() == null)
318             dGroup.setExternalMembers(new HashSet<ExternalEntity>());
319 
320         dGroup.getExternalMembers().add(getCorrespondingExternalEntity(user));
321     }
322 
323     protected ExternalEntity getCorrespondingExternalEntity(final User user) throws RepositoryException
324     {
325         if (user == null)
326             throw new IllegalArgumentException("Input (user) is null.");
327 
328         ExternalEntity result = externalEntityDao.getExternalEntity(user.getName());
329 
330         if (result == null)
331             return externalEntityDao.createExternalEntity(user.getName());
332         else
333             return result;
334     }
335 
336     public boolean hasMembership(Group group, User user) throws EntityException
337     {
338         if (group==null || getGroup(group) == null)
339             return false;
340 
341         validateGroupAndUser(group, user);
342 
343         final DefaultHibernateGroup defGroup = getGroupInSession(group);
344 
345         if (isUserExternal(user))
346         {
347             return hasExternalMembership(defGroup, user);
348         }
349         else
350         {
351             return hasLocalMembership(defGroup, (DefaultHibernateUser) user);
352         }
353     }
354 
355     protected void validateGroupAndUser(Group group, User user) throws EntityException
356     {
357         if (group == null)
358             throw new IllegalArgumentException("Can't add membership for null group");
359 
360         if (getGroup(group) == null)
361             throw new IllegalArgumentException("Group unknown: [" + group + "] in [" + identifier.getKey() + "]");
362 
363         if (user == null)
364             throw new IllegalArgumentException("User unknown: [" + user + "] in [" + identifier.getKey() + "]");
365 
366         if (!isHandledGroup(group))
367             throw new IllegalArgumentException("Group is not a Hibernate entity [" + group.getClass().getName());
368     }
369 
370     protected boolean hasExternalMembership(final DefaultHibernateGroup defGroup, final User user) throws EntityException
371     {
372         try
373         {
374             return (Boolean)getHibernateTemplate().execute(new HibernateCallback()
375             {
376                 public Object doInHibernate(final Session session) throws HibernateException
377                 {
378                     final Query queryObject = session.getNamedQuery("atluser.externalEntity_hasMembership");
379                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
380                     queryObject.setLong(GROUPID_FIELD, defGroup.getId());
381                     queryObject.setString(EXTERNAL_ENTITY_NAME_FIELD, user.getName());
382                     Integer count = (Integer)queryObject.uniqueResult();
383                     return count != null && count > 0;
384                 }
385             });
386         }
387         catch (DataAccessException e)
388         {
389             throw new RepositoryException(e);
390         }
391     }
392 
393     protected boolean hasLocalMembership(DefaultHibernateGroup defGroup, DefaultHibernateUser defUser) throws EntityException
394     {
395         Collection usersGroups = getAllGroupsForUser(defUser);
396 
397         return (usersGroups != null && usersGroups.contains(defGroup));
398     }
399 
400     public void removeMembership(Group group, User user) throws EntityException
401     {
402         validateGroupAndUser(group, user);
403 
404         Group groupInSession = getGroupInSession(group);
405 
406         DefaultHibernateGroup hibernateGroup = (DefaultHibernateGroup) groupInSession;
407 
408         HibernateTemplate hibernateTemplate = getHibernateTemplate();
409         if (isUserExternal(user))
410         {
411             ExternalEntity extUser = getCorrespondingExternalEntity(user);
412 
413             // remove user from group
414             hibernateGroup.getExternalMembers().remove(extUser);
415 
416         }
417         else
418         {
419             if(USE_EXPERIMENTAL_MAPPINGS) {
420                 DefaultHibernateUser huser = (DefaultHibernateUser)user;
421                 huser.getGroups().remove(groupInSession);
422                 hibernateTemplate.saveOrUpdate(huser);
423             }
424 
425             else {
426             // remove user from group
427             hibernateGroup.getLocalMembers().remove(user);
428             hibernateTemplate.saveOrUpdate(groupInSession);
429             }
430         }
431 
432         hibernateTemplate.flush();
433     }
434 
435     public boolean isReadOnly(Group group) throws EntityException
436     {
437         return (getGroup(group) == null);
438     }
439 
440     public boolean supportsExternalMembership() throws EntityException
441     {
442         return true;
443     }
444 
445     /**
446      * @return the {@link com.atlassian.user.repository.RepositoryIdentifier} which is managed by this instance.
447      */
448     public RepositoryIdentifier getIdentifier()
449     {
450         return identifier;
451     }
452 
453     public RepositoryIdentifier getRepository(Entity entity) throws EntityException
454     {
455         if (getGroup(entity.getName()) != null)
456             return identifier;
457 
458         return null;
459     }
460 
461     /**
462      * Used to detemine whether an entity can be added (eg, can call {@link com.atlassian.user.UserManager#createUser(String)} or
463      * {@link com.atlassian.user.GroupManager#createGroup(String)}
464      */
465     public boolean isCreative()
466     {
467         return true;
468     }
469 
470     /**
471      * Used to get a group from Hibernate (in session) from the "cached" version.
472      * Ensures that we do not have un-attached group objects.
473      */
474     private DefaultHibernateGroup getGroupInSession(Group group)
475             throws EntityException
476     {
477         if (group == null)
478             throw new IllegalArgumentException("Input (group) is null.");
479         else if (!isHandledGroup(group))
480             throw new IllegalArgumentException("Group is not a Hibernate entity [" + group.getClass().getName());
481 
482         return getGroup(group);
483     }
484 
485     @SuppressWarnings("unchecked")
486     private List<Group> getGroupsFromHibernate()
487     {
488         return getHibernateTemplate().executeFind(new HibernateCallback()
489         {
490             public Object doInHibernate(Session session) throws HibernateException
491             {
492                 Query queryObject = session.getNamedQuery("atluser.group_findAll");
493                 SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
494                 return queryObject.list();
495             }
496         });
497     }
498 
499 
500     @SuppressWarnings("unchecked")
501     private List<Group> getLocalUserGroupsFromHibernate(final DefaultHibernateUser defUser) throws RepositoryException
502     {
503         try
504         {
505             return getHibernateTemplate().executeFind(new HibernateCallback()
506             {
507                 public Object doInHibernate(Session session) throws HibernateException
508                 {
509                     Query queryObject = session.getNamedQuery("atluser.group_getGroupsForUser");
510                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
511                     queryObject.setLong(ENTITYID_FIELD, defUser.getId());
512 
513                     return queryObject.list();
514                 }
515             });
516         }
517         catch (DataAccessException e)
518         {
519             throw new RepositoryException(e);
520         }
521     }
522 
523     @SuppressWarnings("unchecked")
524     private List<Group> getExternalUserGroupsFromHibernate(final ExternalEntity externalEntity) throws RepositoryException
525     {
526         try
527         {
528             return getHibernateTemplate().executeFind(new HibernateCallback()
529             {
530                 public Object doInHibernate(Session session) throws HibernateException
531                 {
532                     Query queryObject = session.getNamedQuery("atluser.group_getGroupsForExternalEntity");
533                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
534 
535                     if (externalEntity != null)
536                         queryObject.setLong(ENTITYID_FIELD, externalEntity.getId());
537 
538                     return queryObject.list();
539                 }
540             });
541         }
542         catch (DataAccessException e)
543         {
544             throw new RepositoryException(e);
545         }
546     }
547 
548     @SuppressWarnings("unchecked")
549     private List<String> getLocalMemberNamesFromHibernate(final DefaultHibernateGroup defGroup) throws RepositoryException
550     {
551         try
552         {
553             return getHibernateTemplate().executeFind(new HibernateCallback()
554             {
555                 public Object doInHibernate(Session session) throws HibernateException
556                 {
557                     Query queryObject = session.getNamedQuery("atluser.group_getLocalMemberNames");
558                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
559 
560                     if (defGroup != null)
561                         queryObject.setLong(GROUPID_FIELD, defGroup.getId());
562 
563                     return queryObject.list();
564                 }
565             });
566         }
567         catch (DataAccessException e)
568         {
569             throw new RepositoryException(e);
570         }
571     }
572 
573     @SuppressWarnings("unchecked")
574     private List<String> getExternalMemberNamesFromHibernate(final DefaultHibernateGroup defGroup)
575             throws RepositoryException
576     {
577         List result;
578         try
579         {
580             result = getHibernateTemplate().executeFind(new HibernateCallback()
581             {
582                 public Object doInHibernate(Session session) throws HibernateException
583                 {
584                     Query queryObject = session.getNamedQuery("atluser.group_getExternalMemberNames");
585                     SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
586 
587                     if (defGroup != null)
588                         queryObject.setLong(GROUPID_FIELD, defGroup.getId());
589 
590                     return queryObject.list();
591                 }
592             });
593         }
594         catch (DataAccessException e)
595         {
596             throw new RepositoryException(e);
597         }
598         return result;
599     }
600 }