gt:     if (gd.isDifferent()) {
context: /*******************************************************************************
   * Copyright (c) 2013, 2014 Lectorius, Inc.
   * Authors:
   * Vijay Pandurangan (vijayp@mitro.co)
   * Evan Jones (ej@mitro.co)
   * Adam Hilss (ahilss@mitro.co)
   *
   *
   *     This program is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU General Public License as published by
   *     the Free Software Foundation, either version 3 of the License, or
   *     (at your option) any later version.
   *
   *     This program is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU General Public License for more details.
   *
   *     You should have received a copy of the GNU General Public License
   *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *     
   *     You can contact the authors at inbound@mitro.co.
   *******************************************************************************/
  package co.mitro.core.servlets;
  
  import java.io.IOException;
  import java.math.BigInteger;
  import java.security.SecureRandom;
  import java.sql.SQLException;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Map;
  import java.util.Set;
  
  import javax.servlet.annotation.WebServlet;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import co.mitro.core.accesscontrol.AuthenticatedDB;
  import co.mitro.core.exceptions.MitroServletException;
  import co.mitro.core.server.data.DBAcl;
  import co.mitro.core.server.data.DBGroup;
  import co.mitro.core.server.data.DBIdentity;
  import co.mitro.core.server.data.DBPendingGroup;
  import co.mitro.core.server.data.RPC.AddPendingGroupRequest;
  import co.mitro.core.server.data.RPC.AddPendingGroupRequest.AdminInfo;
  import co.mitro.core.server.data.RPC.AddPendingGroupRequest.MemberList;
  import co.mitro.core.server.data.RPC.AddPendingGroupRequest.PendingGroup;
  import co.mitro.core.server.data.RPC.AddPendingGroupResponse;
  import co.mitro.core.server.data.RPC.GroupDiff;
  import co.mitro.core.server.data.RPC.GroupDiff.GroupModificationType;
  import co.mitro.core.server.data.RPC.MitroRPC;
  
  import com.google.common.base.Strings;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  import com.google.common.collect.Sets;
  import com.j256.ormlite.stmt.DeleteBuilder;
  import com.j256.ormlite.stmt.SelectArg;
  
  @WebServlet("/api/internal/AddPendingGroups")
  public class AddPendingGroupServlet extends MitroServlet {
    private static final long serialVersionUID = -6743381126604749211L;
    private static final Logger logger = LoggerFactory.getLogger(AddPendingGroupServlet.class);
    private static final SecureRandom secureRng = new SecureRandom(); 
    
    @Override
    protected MitroRPC processCommand(MitroRequestContext context) throws IOException, SQLException, MitroServletException {
      AddPendingGroupRequest in = gson.fromJson(context.jsonRequest,
          AddPendingGroupRequest.class);
      
      /** This is generated once per push per scope. Used to prevent race conditions*/
      final String nonce = new BigInteger(128, secureRng).toString(32);
  
      assert (in.pendingGroups != null);
      assert (in.adminInfo != null);
      final AdminInfo adminInfo = in.adminInfo;
      assert (adminInfo.domainAdminEmail != null);
      DBIdentity domainAdmin = DBIdentity.getIdentityForUserName(context.manager, adminInfo.domainAdminEmail);
      if (domainAdmin == null) {
        throw new MitroServletException("unknown user:" + adminInfo.domainAdminEmail);
      }
      if (Strings.isNullOrEmpty(in.scope)) {
        throw new MitroServletException("scope must not be null or empty");
      }
  
      @SuppressWarnings("deprecation")
      AuthenticatedDB syncAdminDb = AuthenticatedDB.deprecatedNew(context.manager, domainAdmin);
      Set<DBGroup> orgs = syncAdminDb.getOrganizations();
      if (orgs.isEmpty()) {
        throw new MitroServletException("no org:" + adminInfo.domainAdminEmail);
      } else if (orgs.size() > 1) {
        throw new MitroServletException("user " + adminInfo.domainAdminEmail + 
            " is an admin of multiple orgs. aborting");
      }
      DBGroup org = orgs.iterator().next();
      Map<String, DBGroup> existingGroups = Maps.newHashMap();
      Map<String, GroupDiff> diffs = Maps.newHashMap();
      Map<String, MemberList> pendingGroupMap = Maps.newHashMap();
      Map<String, PendingGroup> inMap = Maps.newHashMap();
      for (PendingGroup pg : in.pendingGroups) {
        inMap.put(pg.groupName, pg);
      }
  
      calculatePendingGroupDiffs(context, in.pendingGroups, org, existingGroups,
          diffs, pendingGroupMap, in.scope);
  
      // send email if there are no existing sync requests.
      boolean alreadySentEmail = (null != context.manager.pendingGroupDao.queryBuilder().where().eq(
          DBPendingGroup.SCOPE_NAME, new SelectArg(in.scope)).queryForFirst());
      if (!(alreadySentEmail || diffs.isEmpty())) {
        // TODO SEND EMAIL;
      }
  
      // TODO: send email!
      DeleteBuilder<DBPendingGroup, Integer> deleter = context.manager.pendingGroupDao.deleteBuilder();
      deleter.where().eq(DBPendingGroup.SCOPE_NAME, in.scope);
      deleter.delete();
  
      if (!diffs.isEmpty()) {
        // if there are _any_ diffs, we have to add everything.
        for (String groupName : pendingGroupMap.keySet()) {
          PendingGroup pg = inMap.get(groupName);
          DBPendingGroup dbg = new DBPendingGroup(context.requestor, groupName, in.scope, pg.memberListJson, pg.signature, org);
          dbg.setSyncNonce(nonce);
          context.manager.pendingGroupDao.create(dbg);
        }
      }
      
      AddPendingGroupResponse out = new AddPendingGroupResponse();
      out.syncNonce = nonce;
      out.diffs = diffs;
      return out;
    }
  
    /**
     * Too many inout parameters.
     * TODO: change this to use a parameter object
     * @param context in
     * @param pendingGroupsList in
     * @param org in
     * @param existingGroups in/out
     * @param diffs in/out
     * @param pendingGroupMap in/out
     * @return the scope
     */
    public static void calculatePendingGroupDiffs(MitroRequestContext context,
        Collection<? extends PendingGroup> pendingGroupsList, DBGroup org,
        Map<String, DBGroup> existingGroups, Map<String, GroupDiff> diffs,
        Map<String, MemberList> pendingGroupMap, String scope) throws MitroServletException,
        SQLException {
      assert scope != null || pendingGroupsList.isEmpty();
      for (PendingGroup pg : pendingGroupsList) {
        AddPendingGroupRequest.MemberList ml = gson.fromJson(pg.memberListJson,
            AddPendingGroupRequest.MemberList.class);
        assert (null != ml) : "you cannot have a null MemberList";
        for (String user : ml.memberList) {
          assert Util.isEmailAddress(user) : "invalid email: '" + user + "'";
        }
        pendingGroupMap.put(pg.groupName, ml);
      }
      
      for (DBGroup g : org.getAllOrgGroups(context.manager)) {
        if (scope != null && scope.equals(g.getScope())) {
          existingGroups.put(g.getName(), g);
        }
      }
      
      Set<String> allGroupNames = Sets.union(pendingGroupMap.keySet(), existingGroups.keySet());
      for (String groupName : allGroupNames) {
        diffGroupPair(context, diffs, groupName, existingGroups.get(groupName), pendingGroupMap.get(groupName));
      }
    }
  
    private static void diffGroupPair(MitroRequestContext context, Map<String, GroupDiff> diffs,
        String groupName, DBGroup existing, MemberList pending)
        throws SQLException {
      GroupDiff gd = new GroupDiff();
      gd.groupName = groupName;
      Set<String> syncSourceMembers = null;
      Set<String> existingUsers = null;
      if (existing != null) {
        Set<Integer> userIds = Sets.newHashSet();
        existing.putDirectUsersIntoSet(userIds, DBAcl.modifyGroupSecretsAccess());
        // TODO: this will pull in users n*m times where n is number of users and m
        //       is number of groups the user is in.
        existingUsers = DBIdentity.getUserNamesFromIds(context.manager, userIds);
      }
      if (pending != null) {
        syncSourceMembers = Sets.newHashSet(pending.memberList);
      }
      assert (syncSourceMembers != null || existingUsers != null);
      if (syncSourceMembers == null) {
        gd.groupModification  = GroupModificationType.IS_DELETED;
        syncSourceMembers = Collections.emptySet();
      } else if (existingUsers == null) {
        gd.groupModification  = GroupModificationType.IS_NEW;
        existingUsers = Collections.emptySet();
      }
      gd.deletedUsers = Lists.newArrayList(Sets.difference(existingUsers, syncSourceMembers));
      gd.newUsers = Lists.newArrayList(Sets.difference(syncSourceMembers, existingUsers));
      if (gd.groupModification.equals(GroupModificationType.IS_UNCHANGED) &&
            !(gd.deletedUsers.isEmpty() && gd.newUsers.isEmpty())) {
        gd.groupModification = GroupModificationType.MEMBERSHIP_MODIFIED;
      }
  