View Javadoc

1   package com.atlassian.user.util.migration;
2   
3   import java.sql.Connection;
4   import java.sql.ResultSet;
5   import java.sql.SQLException;
6   import java.util.HashMap;
7   import java.util.Iterator;
8   import java.util.List;
9   import java.util.Map;
10  
11  import javax.sql.DataSource;
12  
13  import net.sf.hibernate.HibernateException;
14  import net.sf.hibernate.Session;
15  import net.sf.hibernate.SessionFactory;
16  
17  import org.apache.log4j.Logger;
18  import org.springframework.jdbc.core.JdbcTemplate;
19  import org.springframework.jdbc.core.RowCallbackHandler;
20  import org.springframework.jdbc.datasource.SingleConnectionDataSource;
21  import org.springframework.orm.hibernate.SessionFactoryUtils;
22  
23  import com.atlassian.user.EntityException;
24  import com.atlassian.user.ExternalEntity;
25  import com.atlassian.user.Group;
26  import com.atlassian.user.GroupManager;
27  import com.atlassian.user.User;
28  import com.atlassian.user.UserManager;
29  import com.atlassian.user.configuration.DefaultDelegationAccessor;
30  import com.atlassian.user.configuration.DelegationAccessor;
31  import com.atlassian.user.configuration.RepositoryAccessor;
32  import com.atlassian.user.impl.DefaultUser;
33  import com.atlassian.user.impl.RepositoryException;
34  import com.atlassian.user.impl.hibernate.DefaultExternalEntityDAO;
35  import com.atlassian.user.impl.hibernate.DefaultHibernateUser;
36  import com.atlassian.user.impl.hibernate.properties.HibernatePropertySetFactory;
37  import com.atlassian.user.impl.osuser.OSUAccessor;
38  import com.atlassian.user.impl.osuser.OSUUserManager;
39  import com.opensymphony.user.provider.AccessProvider;
40  
41  /**
42   * <p>
43   * Makes a raw JDBC connection to os_user tables and copies across information into the supplied
44   * {@link com.atlassian.user.UserManager}, {@link com.atlassian.user.GroupManager}, and
45   * {@link com.atlassian.user.properties.PropertySetFactory}
46   * </p>
47   * <p>
48   * <strong>Note</strong> that this class is not safe for multi-threaded use since it maintains reference to the
49   * HibernateSession it is running within for it's duration.
50   * </p>
51   */
52  
53  public class OSUEntityMigrator implements EntityMigrator
54  {
55      private static final Logger log = Logger.getLogger(OSUEntityMigrator.class);
56  
57      private static final String OSUSER_REPOSITORY_KEY = "osuserRepository";
58  
59      private UserManager targetUserManager;
60      private GroupManager targetGroupManager;
61  
62      private AccessProvider osAccessProvider;
63  
64      private final SessionFactory sessionFactory;
65  
66      private final DefaultExternalEntityDAO externalEntityDAO;
67      
68      /**
69       * The session that will be used for run of this migrator. A reference is maintained to ensure it doesn't get
70       * garbage collected (and the connection closed) during the run.
71       */
72      private Session hibernateSession;
73  
74      public OSUEntityMigrator(RepositoryAccessor osuserRepositoryAccessor, RepositoryAccessor repositoryAccessor, SessionFactory sessionFactory)
75      {
76          if (osuserRepositoryAccessor == null)
77              throw new IllegalArgumentException("osuserRepositoryAccessor is required.");
78          if (repositoryAccessor == null)
79              throw new IllegalArgumentException("targetRepositoryAccessor is required.");
80          if (sessionFactory == null)
81              throw new IllegalArgumentException("sessionFactory is required.");
82  
83          this.sessionFactory = sessionFactory;
84          this.externalEntityDAO = new DefaultExternalEntityDAO(sessionFactory);
85  
86          final DelegationAccessor targetRepositoryAccessor = getNonOSUserRepositoryAccessor(repositoryAccessor);
87          if (!targetRepositoryAccessor.getRepositoryAccessors().isEmpty())
88          {
89              final UserManager osUserManager = osuserRepositoryAccessor.getUserManager();
90              if (osUserManager == null)
91                  throw new IllegalArgumentException("osUserManager is required.");
92  
93              final OSUAccessor osuAccessor = ((OSUUserManager) osUserManager).getAccessor();
94              if (osuAccessor == null)
95                  throw new IllegalArgumentException("osuAccessor is required.");
96  
97              osAccessProvider = osuAccessor.getAccessProvider();
98              if (osAccessProvider == null)
99                  throw new IllegalArgumentException("osAccessProvider is required.");
100 
101             targetUserManager = targetRepositoryAccessor.getUserManager();
102             targetGroupManager = targetRepositoryAccessor.getGroupManager();
103 
104             if (targetUserManager == null)
105                 throw new IllegalArgumentException("userManager is required.");
106             if (targetGroupManager == null)
107                 throw new IllegalArgumentException("groupManager is required.");
108         }
109     }
110 
111     private DelegationAccessor getNonOSUserRepositoryAccessor(RepositoryAccessor repositoryAccessor)
112     {
113         final DelegationAccessor nonOSUserDelegationAccessor = new DefaultDelegationAccessor();
114         if (repositoryAccessor instanceof DelegationAccessor)
115         {
116             final DelegationAccessor delegationAccessor = (DelegationAccessor) repositoryAccessor;
117             for (Iterator iterator = delegationAccessor.getRepositoryAccessors().iterator(); iterator.hasNext();)
118             {
119                 final RepositoryAccessor accessor = (RepositoryAccessor) iterator.next();
120                 if (!OSUSER_REPOSITORY_KEY.equals(accessor.getIdentifier().getKey()))
121                     nonOSUserDelegationAccessor.addRepositoryAccessor(accessor);
122             }
123             return nonOSUserDelegationAccessor;
124         }
125         else
126         {
127             if (!OSUSER_REPOSITORY_KEY.equals(repositoryAccessor.getIdentifier().getKey()))
128                 nonOSUserDelegationAccessor.addRepositoryAccessor(repositoryAccessor);
129         }
130         return nonOSUserDelegationAccessor;
131     }
132 
133     /**
134      * The method is organised in a 'strange' way for performance reasons. DON'T change it. Every white space is there for a reason :)
135      * The performace problem was: every time we add a member to a hibernate group, hibernate marks this group object as dirty.
136      * Because hibernate group contains the list of all its members and needs to iterate through all of them when flush() is called.
137      * flush() is called every time hibernate thinks it needs to do it, for example when we call getUser() it first calls flush to make
138      * sure we get up to date data. We structured the code in a way so that flush is not called until we add all users to a group.
139      * That's why we cache the list of users. We also rely on the fact that targetGroupManager maintains its own cache and does not need
140      * to call flush() each time we get a group by a groupname.
141      *
142      * @throws RepositoryException if there is no non OSUser repository was included in target repository Accessor
143      */
144     public void migrate(MigratorConfiguration config, MigrationProgressListener progressListener) throws EntityException
145     {
146         if (targetUserManager == null)
147         {
148             throw new RepositoryException("No non OSUser repository configured. Cannot perform migration.");
149         }
150         
151         hibernateSession = SessionFactoryUtils.getSession(sessionFactory, true);        
152 
153         OSUserDao osUserDao = new OSUserDao(getDataSource());
154         final Map<Long, DefaultUser> users = osUserDao.findAllUsers();
155         final Map<String, List<String>> userGroups = osUserDao.findAllUserGroups(users);
156         Map<User, Boolean> migratedUsers = migrateUsers(progressListener, users);
157 
158         // migrate group memberships
159         for (Iterator<Map.Entry<User,Boolean>> it = migratedUsers.entrySet().iterator(); it.hasNext();)
160         {
161             Map.Entry<User,Boolean> userEntry = it.next();
162             final User user = userEntry.getKey();
163             migrateUserGroupMembership(user, userGroups.get(user.getName()), (userEntry.getValue()).booleanValue(), config, progressListener);
164         }
165 
166         // some groups could be empty hence they would not be migrated
167         // when migrating users' membership -- need to migrate them explicitly
168         migrateGroups(progressListener);
169     }
170 
171     private Map<User, Boolean> migrateUsers(MigrationProgressListener progressListener, Map<Long, DefaultUser> users)
172             throws EntityException
173     {
174         // starting user migration
175         progressListener.userMigrationStarted(users.size());
176 
177         Map<User, Boolean> migratedUsers = new HashMap<User, Boolean>();
178 
179         int i = 0;
180         for (Iterator<Map.Entry<Long,DefaultUser>> it = users.entrySet().iterator(); it.hasNext(); i++)
181         {
182             Map.Entry<Long,DefaultUser> userEntry = it.next();
183             final Long osUserId = userEntry.getKey();
184             final User user = userEntry.getValue();
185 
186             User existingUser = targetUserManager.getUser(user.getName());
187             if(existingUser == null)
188             {
189                 User newUser = addUser(targetUserManager, (DefaultUser) user);
190                 migratedUsers.put(newUser, Boolean.TRUE);
191             }
192             else
193             {
194                 migratedUsers.put(existingUser, Boolean.FALSE);
195             }
196             migratePropertySet(osUserId, user);
197 
198 
199             progressListener.userMigrated();
200             if (i % 100 == 0){
201                 try
202                 {
203                     hibernateSession.flush();
204                     hibernateSession.clear();
205                 }
206                 catch (HibernateException e)
207                 {
208                     log.error(e);
209                 }
210             }
211         }
212         // users migrated, starting group migration
213         progressListener.userMigrationComplete();
214         return migratedUsers;
215     }
216 
217     private void migrateGroups(MigrationProgressListener progressListener)
218             throws EntityException
219     {
220         final List groups = osAccessProvider.list();
221         progressListener.groupMigrationStarted(groups.size());
222 
223         for (Iterator groupsIterator = groups.iterator(); groupsIterator.hasNext();)
224         {
225             final String groupName = (String) groupsIterator.next();
226             getOrCreateGroup(groupName);
227             progressListener.groupMigrated();
228         }
229         // group migration complete
230         progressListener.groupMigrationComplete();
231     }
232 
233     private void migrateUserGroupMembership(User user, List<String> userGroups, boolean isCreatedUser, MigratorConfiguration config,
234                                             MigrationProgressListener progressListener) throws EntityException
235     {
236         if (userGroups != null){
237             for(Iterator<String> iter = userGroups.iterator();  iter.hasNext();){
238                 String groupName = iter.next();
239                 Group group = getOrCreateGroup(groupName);
240                 if (isCreatedUser || config.isMigrateMembershipsForExistingUsers())
241                 {
242                     if (log.isInfoEnabled()) log.info("Adding member <" + user.getName() + "> to group <" + groupName + ">");
243                     if (!targetGroupManager.isReadOnly(group))
244                     {
245                         targetGroupManager.addMembership(group, user);
246                     }
247                     else
248                     {
249                         progressListener.readonlyGroupMembershipNotMigrated(group.getName(), user.getName());
250                     }
251                 }
252             }
253         }
254     }
255 
256     private void migratePropertySet(Long userId, User user) throws EntityException
257     {
258         if (log.isInfoEnabled()) log.info("Migrating properties for <" + user.getName() + ">");
259 
260         final User targetUser = targetUserManager.getUser(user.getName());
261         final String entityName = getEntityName(targetUser);
262         final long entityId = getEntityId(targetUser);
263 
264         final JdbcTemplate template = new JdbcTemplate(getDataSource());
265 
266         if (template.queryForInt("SELECT count(*) FROM OS_PROPERTYENTRY WHERE entity_name=? AND entity_id=?", new Object[] {entityName, new Long(entityId)}) == 0)
267         {
268             template.query("SELECT * FROM OS_PROPERTYENTRY WHERE entity_name = 'OSUser_user' AND entity_id = ? AND entity_key <> 'fullName' AND entity_key <> 'email'", new Object[]{userId}, new RowCallbackHandler()
269             {
270                 public void processRow(ResultSet resultSet) throws SQLException
271                 {
272                     template.update("INSERT INTO OS_PROPERTYENTRY (entity_name,entity_id,entity_key,key_type,boolean_val,double_val,string_val,long_val,int_val,date_val) VALUES (?,?,?,?,?,?,?,?,?,?)", new Object[]{
273                         entityName,
274                         new Long(entityId),
275                         resultSet.getString("entity_key"),
276                         new Integer(resultSet.getInt("key_type")),
277                         Boolean.valueOf(resultSet.getBoolean("boolean_val")),
278                         new Double(resultSet.getDouble("double_val")),
279                         resultSet.getString("string_val"),
280                         new Long(resultSet.getLong("long_val")),
281                         new Integer(resultSet.getInt("int_val")),
282                         resultSet.getTimestamp("date_val")
283                     });
284                 }
285             });
286         }
287     }
288 
289     private String getEntityName(User user) throws EntityException
290     {
291         if (isExternalUser(user))
292         {
293             return HibernatePropertySetFactory.EXTERNAL_ENTITY + "_" + user.getName();
294         }
295         return HibernatePropertySetFactory.LOCAL_USER + "_" + user.getName();
296     }
297 
298     private long getEntityId(User user) throws EntityException
299     {
300         if (!isExternalUser(user))
301         {
302             return ((DefaultHibernateUser) user).getId();
303         }
304         ExternalEntity externalEntity = externalEntityDAO.getExternalEntity(user.getName());
305         if (externalEntity != null)
306         {
307             return externalEntity.getId();
308         }
309         return externalEntityDAO.createExternalEntity(user.getName()).getId();
310     }
311 
312     private boolean isExternalUser(User user)
313     {
314         return !(user instanceof DefaultHibernateUser);
315     }
316 
317     /**
318      * Adds the given user using the user manager.
319      *
320      * @param userManager the user manager to add the user to
321      * @param user the user to add
322      * @throws EntityException if a problem occur adding the user in the user manager
323      */
324     private User addUser(UserManager userManager, DefaultUser user) throws EntityException
325     {
326         if (log.isInfoEnabled()) log.info("Adding user <" + user.getName() + ">");
327 
328         final User newUser = userManager.createUser(user.getName());
329         newUser.setFullName(user.getFullName());
330         newUser.setEmail(user.getEmail());
331         newUser.setPassword(user.getPassword());
332         userManager.saveUser(newUser);
333         return newUser;
334     }
335 
336     private Group getOrCreateGroup(String groupName) throws EntityException
337     {
338         Group group = targetGroupManager.getGroup(groupName);
339         if (group == null)
340         {
341             if (log.isInfoEnabled()) log.info("Creating group <" + groupName + ">");
342             group = targetGroupManager.createGroup(groupName);
343         }
344 
345         return group;
346     }
347 
348     private DataSource getDataSource()
349     {
350         Connection conn;
351         try
352         {
353             conn = hibernateSession.connection();
354         }
355         catch (HibernateException e)
356         {
357             throw new RuntimeException(e);
358         }
359         return new SingleConnectionDataSource(conn, true);
360     }
361 }