View Javadoc
1   /*******************************************************************************
2    * Copyright 2014 Internet2
3    *  
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *  
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *  
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   ******************************************************************************/
16  package edu.internet2.middleware.grouper.grouperUi.serviceLogic;
17  
18  import java.io.InputStreamReader;
19  import java.io.Reader;
20  import java.sql.Timestamp;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.apache.commons.csv.CSVRecord;
35  import org.apache.commons.fileupload.FileItem;
36  import org.apache.commons.lang.StringUtils;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  import edu.internet2.middleware.grouper.Group;
41  import edu.internet2.middleware.grouper.GroupFinder;
42  import edu.internet2.middleware.grouper.GrouperSession;
43  import edu.internet2.middleware.grouper.GrouperSourceAdapter;
44  import edu.internet2.middleware.grouper.Member;
45  import edu.internet2.middleware.grouper.Membership;
46  import edu.internet2.middleware.grouper.MembershipFinder;
47  import edu.internet2.middleware.grouper.SubjectFinder;
48  import edu.internet2.middleware.grouper.audit.AuditEntry;
49  import edu.internet2.middleware.grouper.audit.AuditTypeBuiltin;
50  import edu.internet2.middleware.grouper.exception.GrouperSessionException;
51  import edu.internet2.middleware.grouper.grouperUi.beans.api.GuiGroup;
52  import edu.internet2.middleware.grouper.grouperUi.beans.api.GuiSubject;
53  import edu.internet2.middleware.grouper.grouperUi.beans.json.GuiPaging;
54  import edu.internet2.middleware.grouper.grouperUi.beans.json.GuiResponseJs;
55  import edu.internet2.middleware.grouper.grouperUi.beans.json.GuiScreenAction;
56  import edu.internet2.middleware.grouper.grouperUi.beans.json.GuiScreenAction.GuiMessageType;
57  import edu.internet2.middleware.grouper.grouperUi.beans.simpleMembershipUpdate.ImportSubjectWrapper;
58  import edu.internet2.middleware.grouper.grouperUi.beans.ui.GroupContainer;
59  import edu.internet2.middleware.grouper.grouperUi.beans.ui.GroupImportContainer;
60  import edu.internet2.middleware.grouper.grouperUi.beans.ui.GroupImportError;
61  import edu.internet2.middleware.grouper.grouperUi.beans.ui.GroupImportGroupSummary;
62  import edu.internet2.middleware.grouper.grouperUi.beans.ui.GrouperRequestContainer;
63  import edu.internet2.middleware.grouper.grouperUi.beans.ui.TextContainer;
64  import edu.internet2.middleware.grouper.grouperUi.serviceLogic.SimpleMembershipUpdateImportExport.GrouperImportException;
65  import edu.internet2.middleware.grouper.hibernate.AuditControl;
66  import edu.internet2.middleware.grouper.hibernate.GrouperTransactionType;
67  import edu.internet2.middleware.grouper.hibernate.HibernateHandler;
68  import edu.internet2.middleware.grouper.hibernate.HibernateHandlerBean;
69  import edu.internet2.middleware.grouper.hibernate.HibernateSession;
70  import edu.internet2.middleware.grouper.internal.dao.GrouperDAOException;
71  import edu.internet2.middleware.grouper.internal.dao.QueryOptions;
72  import edu.internet2.middleware.grouper.internal.util.GrouperUuid;
73  import edu.internet2.middleware.grouper.j2ee.GrouperRequestWrapper;
74  import edu.internet2.middleware.grouper.j2ee.GrouperUiRestServlet;
75  import edu.internet2.middleware.grouper.membership.MembershipType;
76  import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
77  import edu.internet2.middleware.grouper.misc.GrouperSessionHandler;
78  import edu.internet2.middleware.grouper.privs.AccessPrivilege;
79  import edu.internet2.middleware.grouper.ui.GrouperUiFilter;
80  import edu.internet2.middleware.grouper.ui.exceptions.ControllerDone;
81  import edu.internet2.middleware.grouper.ui.exceptions.NoSessionException;
82  import edu.internet2.middleware.grouper.ui.tags.GrouperPagingTag2;
83  import edu.internet2.middleware.grouper.ui.util.GrouperUiConfig;
84  import edu.internet2.middleware.grouper.ui.util.GrouperUiUserData;
85  import edu.internet2.middleware.grouper.ui.util.GrouperUiUtils;
86  import edu.internet2.middleware.grouper.ui.util.ProgressBean;
87  import edu.internet2.middleware.grouper.userData.GrouperUserDataApi;
88  import edu.internet2.middleware.grouper.util.GrouperCallable;
89  import edu.internet2.middleware.grouper.util.GrouperFuture;
90  import edu.internet2.middleware.grouper.util.GrouperUtil;
91  import edu.internet2.middleware.grouperClient.collections.MultiKey;
92  import edu.internet2.middleware.grouperClient.util.ExpirableCache;
93  import edu.internet2.middleware.subject.Subject;
94  import edu.internet2.middleware.subject.SubjectNotUniqueException;
95  import edu.internet2.middleware.subject.SubjectUtils;
96  
97  /**
98   * operations in the group screen
99   * @author mchyzer
100  *
101  */
102 public class UiV2GroupImport {
103 
104   
105   /** logger */
106   protected static final Log LOG = edu.internet2.middleware.grouper.util.GrouperUtil.getLog(UiV2GroupImport.class);
107 
108   /**
109    * validate import list
110    * @param request
111    * @param response
112    */
113   public void groupImportValidateList(HttpServletRequest request, HttpServletResponse response) {
114     
115     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
116     
117     GrouperSession grouperSession = null;
118   
119     try {
120       
121       grouperSession = GrouperSession.start(loggedInSubject);
122 
123       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
124 
125       String entityList = StringUtils.defaultString(request.getParameter("entityList"));
126       
127       //split trim by comma, semi, or whitespace
128       entityList = StringUtils.replace(entityList, ",", " ");
129       entityList = StringUtils.replace(entityList, ";", " ");
130       
131       String[] entityIdOrIdentifiers = GrouperUtil.splitTrim(entityList, null, true);
132 
133       if (GrouperUtil.length(entityIdOrIdentifiers) == 0) {
134 
135         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
136             "#entityListId",
137             TextContainer.retrieveFromRequest().getText().get("groupImportNoEntitiesSpecified")));
138         return;
139 
140       }
141       
142       if (GrouperUtil.length(entityIdOrIdentifiers) > 100) {
143 
144         guiResponseJs.addAction(GuiScreenAction.newMessage(GuiMessageType.error, 
145             TextContainer.retrieveFromRequest().getText().get("groupImportTooManyEntitiesToValidate")));
146         return;
147 
148       }
149       
150       //extra source ids and subjects ids
151       Set<GuiSubject> extraGuiSubjects = new LinkedHashSet<GuiSubject>();
152       
153       GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer().setGroupImportExtraGuiSubjects(extraGuiSubjects);
154 
155       String source = request.getParameter("searchEntitySourceName");
156       
157       List<String> entityIdOrIdentifiersList = new ArrayList<String>(Arrays.asList(GrouperUtil.nonNull(
158           entityIdOrIdentifiers, String.class)));
159       
160       Map<String, Subject> entityIdOrIdentifierMap = null;
161       
162       if (StringUtils.equals("all", source)) {
163 
164         entityIdOrIdentifierMap = SubjectFinder.findByIdsOrIdentifiers(entityIdOrIdentifiersList);
165         
166       } else {
167 
168         entityIdOrIdentifierMap = SubjectFinder.findByIdsOrIdentifiers(entityIdOrIdentifiersList, source);
169 
170       }
171       
172       //lets add all the subjects
173       for (Subject subject : GrouperUtil.nonNull(entityIdOrIdentifierMap).values()) {
174         extraGuiSubjects.add(new GuiSubject(subject));
175       }
176 
177       //lets see which are missing
178       entityIdOrIdentifiersList.removeAll(GrouperUtil.nonNull(entityIdOrIdentifierMap).keySet());
179       
180       if (entityIdOrIdentifiersList.size() > 0) {
181         GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer().setEntityIdsNotFound(GrouperUtil.join(entityIdOrIdentifiersList.iterator(), ", "));
182         
183         guiResponseJs.addAction(GuiScreenAction.newMessage(GuiMessageType.error, 
184             TextContainer.retrieveFromRequest().getText().get("groupImportEntityIdsNotFound")));
185 
186       }
187 
188       //clear out combobox
189       guiResponseJs.addAction(GuiScreenAction.newScript(
190           "dijit.byId('groupAddMemberComboId').set('displayedValue', ''); " +
191           "dijit.byId('groupAddMemberComboId').set('value', '');"));
192 
193       //select the option for enter in list
194       guiResponseJs.addAction(GuiScreenAction.newFormFieldValue("bulkAddOptions", "input"));
195 
196       //fill in the extra subjects
197       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#groupImportExtraMembersDivId", 
198           "/WEB-INF/grouperUi2/groupImport/groupImportExtraSubjects.jsp"));
199 
200     } finally {
201       GrouperSession.stopQuietly(grouperSession);
202     }
203 
204 
205   }
206   
207 
208   /**
209    * export a group
210    * @param request
211    * @param response
212    */
213   public void groupExportSubmit(HttpServletRequest request, HttpServletResponse response) {
214 
215     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
216     
217     GrouperSession grouperSession = null;
218   
219     Group group = null;
220   
221     try {
222   
223       grouperSession = GrouperSession.start(loggedInSubject);
224 
225       List<String> urlStrings = GrouperUiRestServlet.extractUrlStrings(request);
226       
227       //groupId=721e4e8ae6e54c4087db092f0a6372f7
228       String groupIdString = urlStrings.get(2);
229       
230       String groupId = GrouperUtil.prefixOrSuffix(groupIdString, "=", false);
231       
232       group = GroupFinder.findByUuid(grouperSession, groupId, false);
233 
234       if (group == null) {
235         throw new RuntimeException("Cant find group by id: " + groupId);
236       }
237       
238       GroupContainer groupContainer = GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupContainer();
239       GroupImportContainer groupImportContainer = GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer();
240       
241       groupContainer.setGuiGroup(new GuiGroup(group));
242       
243       if (!groupContainer.isCanRead()) {
244         throw new RuntimeException("Cant read group: " + group.getName());
245       }
246       
247       //ids
248       String groupExportOptions = urlStrings.get(3);
249       
250       boolean exportAll = false;
251       if (StringUtils.equals("all", groupExportOptions)) {
252         groupImportContainer.setExportAll(true);
253         exportAll = true;
254       } else if (StringUtils.equals("ids", groupExportOptions)) {
255         groupImportContainer.setExportAll(false);
256       } else {
257         throw new RuntimeException("Not expecting group-export-options value: '" + groupExportOptions + "'");
258       }
259 
260       
261       //groupExportSubjectIds_removeAllMembers.csv
262       @SuppressWarnings("unused")
263       String fileName = urlStrings.get(4);
264       
265       if (exportAll) {
266         String headersCommaSeparated = GrouperUiConfig.retrieveConfig().propertyValueString(
267             "uiV2.group.exportAllSubjectFields");
268         
269         String exportAllSortField = GrouperUiConfig.retrieveConfig().propertyValueString(
270             "uiV2.group.exportAllSortField");
271   
272         SimpleMembershipUpdateImportExport.exportGroupAllFieldsToBrowser(group, headersCommaSeparated, exportAllSortField, false);
273       } else {
274         
275         SimpleMembershipUpdateImportExport.exportGroupSubjectIdsCsv(group, false);
276         
277       }
278       
279       GrouperUserDataApi.recentlyUsedGroupAdd(GrouperUiUserData.grouperUiGroupNameForUserData(), 
280           loggedInSubject, group);
281 
282     } finally {
283       GrouperSession.stopQuietly(grouperSession);
284     }
285 
286   }
287   
288   /**
289    * export group members screen
290    * @param request
291    * @param response
292    */
293   public void groupExport(HttpServletRequest request, HttpServletResponse response) {
294     
295     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
296     
297     GrouperSession grouperSession = null;
298   
299     Group group = null;
300   
301     try {
302   
303       grouperSession = GrouperSession.start(loggedInSubject);
304   
305       group = UiV2Group.retrieveGroupHelper(request, AccessPrivilege.READ).getGroup();
306       
307       if (group == null) {
308         return;
309       }
310   
311       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
312       
313       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#grouperMainContentDivId", 
314           "/WEB-INF/grouperUi2/groupImport/groupExport.jsp"));
315   
316     } finally {
317       GrouperSession.stopQuietly(grouperSession);
318     }
319   }
320 
321 
322   /**
323    * export group members screen change the type of export
324    * @param request
325    * @param response
326    */
327   public void groupExportTypeChange(HttpServletRequest request, HttpServletResponse response) {
328     
329     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
330     
331     GrouperSession grouperSession = null;
332   
333     Group group = null;
334   
335     try {
336   
337       grouperSession = GrouperSession.start(loggedInSubject);
338   
339       group = UiV2Group.retrieveGroupHelper(request, AccessPrivilege.READ).getGroup();
340       
341       if (group == null) {
342         return;
343       }
344   
345       String groupExportOptions = request.getParameter("group-export-options[]");
346       
347       GroupImportContainer groupImportContainer = GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer();
348       
349       if (StringUtils.equals("all", groupExportOptions)) {
350         groupImportContainer.setExportAll(true);
351       } else if (StringUtils.equals("ids", groupExportOptions)) {
352         groupImportContainer.setExportAll(false);
353       } else {
354         throw new RuntimeException("Not expecting group-export-options value: '" + groupExportOptions + "'");
355       }
356       
357       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
358       
359       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#formActionsDivId", 
360           "/WEB-INF/grouperUi2/groupImport/groupExportButtons.jsp"));
361   
362     } finally {
363       GrouperSession.stopQuietly(grouperSession);
364     }
365   }
366 
367   /**
368    * setup the extra groups (other than combobox), and maybe move the combobox down
369    * @param loggedInSubject
370    * @param request
371    * @param removeGroupId if removing one
372    * @param includeCombobox
373    * @param allGroups, pass in a blank linked hash set, and all groups will be populated including combobox
374    * @param errorOnNullCombobox true if an error should appear if there is nothing in the combobox
375    * @return true if ok, false if not
376    */
377   private boolean groupImportSetupExtraGroups(Subject loggedInSubject, 
378       HttpServletRequest request, GuiResponseJs guiResponseJs, boolean considerRemoveGroupId, boolean includeCombobox,
379       Set<Group> allGroups, boolean errorOnNullCombobox) {
380 
381     Set<GuiGroup> extraGuiGroups = new LinkedHashSet<GuiGroup>();
382     
383     GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer().setGroupImportExtraGuiGroups(extraGuiGroups);
384     
385     String removeGroupId = null;
386 
387     //if removing a group id
388     if (considerRemoveGroupId) {
389       removeGroupId = request.getParameter("removeGroupId");
390       if (StringUtils.isBlank(removeGroupId)) {
391         throw new RuntimeException("Why would removeGroupId be empty????");
392       }
393     }
394 
395     //if moving combobox down to extra list or getting all groups
396     String comboValue = request.getParameter("groupImportGroupComboName");
397     
398     if (StringUtils.isBlank(comboValue)) {
399       //if didnt pick one from results
400       comboValue = request.getParameter("groupImportGroupComboNameDisplay");
401     }
402     
403     Group theGroup = StringUtils.isBlank(comboValue) ? null : new GroupFinder()
404         .assignPrivileges(AccessPrivilege.UPDATE_PRIVILEGES)
405         .assignSubject(loggedInSubject)
406         .assignFindByUuidOrName(true).assignScope(comboValue).findGroup();
407 
408     boolean success = true;
409     
410     if (theGroup == null) {
411       if (includeCombobox && errorOnNullCombobox) {
412         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
413             "#groupImportGroupComboErrorId",
414             TextContainer.retrieveFromRequest().getText().get("groupImportGroupNotFound")));
415         success = false;
416       }
417       
418     } else {
419       if (includeCombobox) {
420         extraGuiGroups.add(new GuiGroup(theGroup));
421       }
422       //always add to all groups
423       allGroups.add(theGroup);
424     }
425 
426     //loop through all the hidden fields (max 100)
427     //TODO cant this loop and the above logic be collapsed?
428     for (int i=0;i<100;i++) {
429       String extraGroupId = request.getParameter("extraGroupId_" + i);
430       
431       //we are at the end
432       if (StringUtils.isBlank(extraGroupId)) {
433         break;
434       }
435       
436       //might be removing this one
437       if (considerRemoveGroupId && StringUtils.equals(removeGroupId, extraGroupId)) {
438         continue;
439       }
440       
441       theGroup = new GroupFinder()
442         .assignPrivileges(AccessPrivilege.UPDATE_PRIVILEGES)
443         .assignSubject(loggedInSubject)
444         .assignFindByUuidOrName(true).assignScope(extraGroupId).findGroup();
445       
446       extraGuiGroups.add(new GuiGroup(theGroup));
447 
448       //always add to all groups
449       allGroups.add(theGroup);
450       
451     }
452     return success;
453   }
454   
455   /**
456    * keep an expirable cache of import progress for 5 hours (longest an import is expected).  This has multikey of session id and some random uuid
457    * uniquely identifies this import as opposed to other imports in other tabs.  This cannot have any request objects or j2ee objects
458    */
459   private static ExpirableCache<MultiKey, GroupImportContainer> importThreadProgress = new ExpirableCache<MultiKey, GroupImportContainer>(300);
460 
461   /**
462    * submit a group import
463    * @param request
464    * @param response
465    */
466   public void groupImportSubmit(HttpServletRequest request, HttpServletResponse response) {
467     
468     Map<String, Object> debugMap = new LinkedHashMap<String, Object>();
469     long startNanos = System.nanoTime();
470     
471     debugMap.put("method", "groupImportSubmit");
472     
473     try {
474       final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
475       
476       GrouperRequestContainer grouperRequestContainer = GrouperRequestContainer.retrieveFromRequestOrCreate();
477   
478       final GroupImportContainer groupImportContainer = grouperRequestContainer.getGroupImportContainer();
479   
480       String sessionId = request.getSession().getId();
481       
482       debugMap.put("sessionId", GrouperUtil.abbreviate(sessionId, 8));
483   
484       
485       // uniquely identifies this import as opposed to other imports in other tabs
486       String uniqueImportId = GrouperUuid.getUuid();
487   
488       debugMap.put("uniqueImportId", GrouperUtil.abbreviate(uniqueImportId, 8));
489   
490       groupImportContainer.setUniqueImportId(uniqueImportId);
491       
492       MultiKey reportMultiKey = new MultiKey(sessionId, uniqueImportId);
493       
494       importThreadProgress.put(reportMultiKey, groupImportContainer);
495       
496       GrouperSession grouperSession = null;
497   
498       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
499   
500       final String bulkAddOption = request.getParameter("bulkAddOptions");
501   
502       
503       //TODO should this be called "groupsTheUserCanUpdate" ?
504       final Set<Group> groups = new LinkedHashSet<Group>();
505       final Set<Subject> subjectSet = new LinkedHashSet<Subject>();
506       final Map<String, Integer> listInvalidSubjectIdsAndRow = new LinkedHashMap<String, Integer>();
507       
508       final boolean importReplaceMembers = GrouperUtil.booleanValue(request.getParameter("replaceExistingMembers"), false);
509       final boolean removeMembers = GrouperUtil.booleanValue(request.getParameter("removeMembers"), false);
510   
511       final Timestamp startDate;
512       try {
513         String startDateString = request.getParameter("startDate");
514         startDate = GrouperUtil.stringToTimestampTimeRequiredWithoutSeconds(startDateString);
515       } catch (Exception e) {
516         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error,
517             "#member-start-date",
518             TextContainer.retrieveFromRequest().getText().get("groupImportFromDateInvalid")));
519         return;
520       }
521 
522       final Timestamp endDate;
523       try {
524         String endDateString = request.getParameter("endDate");
525         endDate = GrouperUtil.stringToTimestampTimeRequiredWithoutSeconds(endDateString);
526       } catch (Exception e) {
527         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error,
528             "#member-end-date",
529             TextContainer.retrieveFromRequest().getText().get("groupImportToDateInvalid")));
530         return;
531       }
532       
533       if (startDate != null && endDate != null && !endDate.after(startDate)) {
534         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error,
535             "#member-end-date",
536             TextContainer.retrieveFromRequest().getText().get("groupImportToDateAfterFromDateError")));
537         return;
538       }
539       
540       final Object[] csvEntriesObject = new Object[1];
541   
542       final String[] fileName = new String[1];
543   
544       try {
545         grouperSession = GrouperSession.start(loggedInSubject);
546   
547         boolean success = groupImportSetupExtraGroups(loggedInSubject, request, guiResponseJs, false, true, groups, false);
548         
549         if (!success) {
550           //error message already shown
551           return;
552         }
553   
554         
555         debugMap.put("groups", groups.size());
556 
557         if (groups.size() == 0) {
558           guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
559               "#groupImportGroupComboErrorId",
560               TextContainer.retrieveFromRequest().getText().get("groupImportGroupNotFound")));
561           return;
562         }
563   
564         // can be import, input, list
565         debugMap.put("bulkAddOption", bulkAddOption);
566   
567         if (StringUtils.equals(bulkAddOption, "import")) {
568           
569           GrouperRequestWrapper grouperRequestWrapper = GrouperRequestWrapper.retrieveGrouperRequestWrapper(request);
570           
571           FileItem importCsvFile = grouperRequestWrapper.getParameterFileItem("importCsvFile");
572   
573           if (importCsvFile == null) {
574             
575             guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
576                 "#importCsvFileId",
577                 TextContainer.retrieveFromRequest().getText().get("groupImportUploadFile")));
578             return;
579           }
580           
581           Reader reader = null;
582           reader = new InputStreamReader(importCsvFile.getInputStream());
583           
584           fileName[0] = StringUtils.defaultString(importCsvFile == null ? "" : importCsvFile.getName());
585   
586           try {
587             
588             List<CSVRecord> csvEntries = SimpleMembershipUpdateImportExport.parseCsvImportFileToCsv(reader, fileName[0]);
589             debugMap.put("csvEntries", GrouperUtil.length(csvEntries));
590             csvEntriesObject[0] = csvEntries;
591           } catch (GrouperImportException gie) {
592             if (LOG.isDebugEnabled()) {
593               LOG.debug("error in import", gie);
594             }
595             guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
596                 "#importCsvFileId", GrouperUtil.xmlEscape(gie.getMessage())));
597             return;
598           }
599           
600         } else if (StringUtils.equals(bulkAddOption, "input")) {
601   
602           //combobox
603           success = groupImportSetupExtraSubjects(loggedInSubject, request, guiResponseJs, false, true, subjectSet, false);
604           
605           if (!success) {
606             //error message already shown
607             return;
608           }
609           
610           if (subjectSet.size() == 0) {
611             guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
612                 "#groupAddMemberComboErrorId", 
613                 TextContainer.retrieveFromRequest().getText().get("groupImportSubjectNotFound")));
614             return;
615           }
616   
617   
618         } else if (StringUtils.equals(bulkAddOption, "list")) {
619   
620           String entityList = StringUtils.defaultString(request.getParameter("entityList"));
621           
622           //split trim by comma, semi, or whitespace
623           entityList = StringUtils.replace(entityList, ",", " ");
624           entityList = StringUtils.replace(entityList, ";", " ");
625           
626           String[] entityIdOrIdentifiers = GrouperUtil.splitTrim(entityList, null, true);
627 
628           debugMap.put("entityIdOrIdentifiers", GrouperUtil.length(entityIdOrIdentifiers));
629           if (GrouperUtil.length(entityIdOrIdentifiers) == 0) {
630   
631             guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
632                 "#entityListId",
633                 TextContainer.retrieveFromRequest().getText().get("groupImportNoEntitiesSpecified")));
634             return;
635   
636           }
637           
638           String source = request.getParameter("searchEntitySourceName");
639           
640           List<String> entityIdOrIdentifiersList = new ArrayList<String>(Arrays.asList(GrouperUtil.nonNull(
641               entityIdOrIdentifiers, String.class)));
642           
643           Map<String, Subject> entityIdOrIdentifierMap = null;
644           
645           if (StringUtils.equals("all", source)) {
646   
647             entityIdOrIdentifierMap = SubjectFinder.findByIdsOrIdentifiers(entityIdOrIdentifiersList);
648             
649           } else {
650   
651             entityIdOrIdentifierMap = SubjectFinder.findByIdsOrIdentifiers(entityIdOrIdentifiersList, source);
652   
653           }
654           
655           //lets add all the subjects
656           subjectSet.addAll(GrouperUtil.nonNull(entityIdOrIdentifierMap).values());
657   
658           //lets see which are missing
659           List<String> originalIdList = new ArrayList<String>(entityIdOrIdentifiersList);
660   
661           //lets see which are missing
662           entityIdOrIdentifiersList.removeAll(GrouperUtil.nonNull(entityIdOrIdentifierMap).keySet());
663   
664           //keep trac of the index of the invalid ids
665           for (String invalidId : entityIdOrIdentifiersList) {
666             int index = originalIdList.indexOf(invalidId);
667             listInvalidSubjectIdsAndRow.put(invalidId, index == -1 ? null : index);
668           }
669           
670         } else {
671           throw new RuntimeException("Not expecting bulk add option: " + bulkAddOption);
672         }
673   
674         {
675           Group group = UiV2Group.retrieveGroupHelper(request, AccessPrivilege.UPDATE, false).getGroup();
676           if (group != null) {
677             groupImportContainer.setImportFromGroup(true);
678             groupImportContainer.setGroupId(group.getId());
679           }
680         }
681         {
682           Subject subject = UiV2Subject.retrieveSubjectHelper(request, false);
683           if (subject != null) {
684             groupImportContainer.setImportFromSubject(true);
685             groupImportContainer.setSubjectId(subject.getId());
686             groupImportContainer.setSourceId(subject.getSourceId());
687           }
688         }
689   
690         if (importReplaceMembers && removeMembers) {
691           guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
692               "#replaceExistingMembersId",
693               TextContainer.retrieveFromRequest().getText().get("groupImportCantReplaceAndRemove")));
694           return;
695         }
696         
697         Iterator<Group> groupIterator = groups.iterator();
698   
699         //TODO first off, why checking VIEW?  should it be READ?  or just UPDATE?
700         //TODO second, are groups not checked for UPDATE above in groupImportSetupExtraGroups()?  or is it just groups added from gruop screen?
701         
702         //lets go through the groups that were submitted
703         while (groupIterator.hasNext()) {
704   
705           final Group group = groupIterator.next();
706   
707           {
708             //remove groups that cannot be viewed
709             boolean canView = (Boolean)GrouperSession.callbackGrouperSession(
710               GrouperSession.staticGrouperSession().internal_getRootSession(), new GrouperSessionHandler() {
711     
712                 @Override
713                 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
714                   return group.canHavePrivilege(loggedInSubject, AccessPrivilege.VIEW.getName(), false);
715                 }
716               });
717     
718             if (!canView) {
719               guiResponseJs.addAction(GuiScreenAction.newMessage(GuiMessageType.error,  
720                   TextContainer.retrieveFromRequest().getText().get("groupImportGroupCantView")));
721               groupIterator.remove();
722               continue;
723             }
724           }
725   
726           {
727             //give error if cant update
728             boolean canUpdate = (Boolean)GrouperSession.callbackGrouperSession(
729               GrouperSession.staticGrouperSession().internal_getRootSession(), new GrouperSessionHandler() {
730   
731                 @Override
732                 public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
733                   return group.canHavePrivilege(loggedInSubject, AccessPrivilege.UPDATE.getName(), false);
734                 }
735               });
736   
737             if (!canUpdate) {
738               guiResponseJs.addAction(GuiScreenAction.newMessage(GuiMessageType.error,  
739                   TextContainer.retrieveFromRequest().getText().get("groupImportGroupCantUpdate")));
740               continue;
741             }
742           }
743         }
744         
745       } catch (Exception e) {
746         throw new RuntimeException("error", e);
747   
748   
749       } finally {
750         GrouperSession.stopQuietly(grouperSession);
751       }
752       
753       GrouperCallable<Void> grouperCallable = new GrouperCallable<Void>("groupImportMembers") {
754 
755         @Override
756         public Void callLogic() {
757           try {
758             groupImportContainer.getProgressBean().setStartedMillis(System.currentTimeMillis());
759 
760             UiV2GroupImport.this.groupImportSubmitHelper(loggedInSubject, groupImportContainer, groups, subjectSet, 
761                 listInvalidSubjectIdsAndRow, removeMembers, importReplaceMembers, bulkAddOption, fileName[0], (List<CSVRecord>)csvEntriesObject[0],
762                 startDate, endDate);
763           } catch (RuntimeException re) {
764             groupImportContainer.getProgressBean().setHasException(true);
765             // log this since the thread will just end and will never get logged
766             LOG.error("error", re);
767           } finally {
768             // we done
769             groupImportContainer.getProgressBean().setComplete(true);
770           }
771           return null;
772         }
773       };      
774       
775       // see if running in thread
776       boolean useThreads = GrouperUiConfig.retrieveConfig().propertyValueBooleanRequired("grouperUi.import.useThread");
777       debugMap.put("useThreads", useThreads);
778 
779       if (useThreads) {
780         
781         GrouperFuture<Void> grouperFuture = GrouperUtil.executorServiceSubmit(GrouperUtil.retrieveExecutorService(), grouperCallable);
782         
783         Integer waitForCompleteForSeconds = GrouperUiConfig.retrieveConfig().propertyValueInt("grouperUi.import.progressStartsInSeconds");
784         debugMap.put("waitForCompleteForSeconds", waitForCompleteForSeconds);
785 
786         GrouperFuture.waitForJob(grouperFuture, waitForCompleteForSeconds);
787         
788         debugMap.put("threadAlive", !grouperFuture.isDone());
789 
790       } else {
791         grouperCallable.callLogic();
792       }
793   
794       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#grouperMainContentDivId", 
795           "/WEB-INF/grouperUi2/groupImport/groupImportReportWrapper.jsp"));
796       
797       groupImportReportStatusHelper(sessionId, uniqueImportId);
798     } catch (RuntimeException re) {
799       debugMap.put("exception", GrouperUtil.getFullStackTrace(re));
800       throw re;
801     } finally {
802       if (LOG.isDebugEnabled()) {
803         debugMap.put("tookMillis", (System.nanoTime()-startNanos)/1000000);
804         LOG.debug(GrouperUtil.mapToString(debugMap));
805       }
806     }
807   }
808 
809   /**
810    * get the status of a report
811    * @param request
812    * @param response
813    */
814   public void groupImportReportStatus(HttpServletRequest request, HttpServletResponse response) {
815     String sessionId = request.getSession().getId();
816     String uniqueImportId = request.getParameter("uniqueImportId");
817     groupImportReportStatusHelper(sessionId, uniqueImportId);
818   }
819   
820   /**
821    * get the status of a report
822    * @param request
823    * @param response
824    */
825   private void groupImportReportStatusHelper(String sessionId, String uniqueImportId) {
826     
827     Map<String, Object> debugMap = new LinkedHashMap<String, Object>();
828     
829     debugMap.put("method", "groupImportReportStatus");
830     debugMap.put("sessionId", GrouperUtil.abbreviate(sessionId, 8));
831     debugMap.put("uniqueImportId", GrouperUtil.abbreviate(uniqueImportId, 8));
832 
833     long startNanos = System.nanoTime();
834     try {
835       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
836   
837       MultiKey reportMultiKey = new MultiKey(sessionId, uniqueImportId);
838       
839       GroupImportContainer groupImportContainer = importThreadProgress.get(reportMultiKey);
840       
841       GrouperRequestContainer.retrieveFromRequestOrCreate().setGroupImportContainer(groupImportContainer);
842   
843       //show the report screen
844       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#id_"+uniqueImportId, 
845           "/WEB-INF/grouperUi2/groupImport/groupImportReport.jsp"));
846       // guiResponseJs.addAction(GuiScreenAction.newScript("guiScrollTop()"));
847 
848       debugMap.put("percentComplete", groupImportContainer.getProgressBean().getPercentComplete());
849       debugMap.put("progressCompleteRecords", groupImportContainer.getProgressBean().getProgressCompleteRecords());
850       debugMap.put("progressTotalRecords", groupImportContainer.getProgressBean().getProgressTotalRecords());
851       
852 
853       if (groupImportContainer != null) {
854         
855         // endless loop?
856         if (groupImportContainer.getProgressBean().isThisLastStatus()) {
857           return;
858         }
859         
860         if (groupImportContainer.getProgressBean().isHasException()) {
861           guiResponseJs.addAction(GuiScreenAction.newMessage(GuiMessageType.error, 
862               TextContainer.retrieveFromRequest().getText().get("groupImportException")));
863           // it has an exception, leave it be
864           importThreadProgress.put(reportMultiKey, null);
865           return;
866         }
867         // kick it off again?
868         debugMap.put("complete", groupImportContainer.getProgressBean().isComplete());
869         if (!groupImportContainer.getProgressBean().isComplete()) {
870           int progressRefreshSeconds = GrouperUiConfig.retrieveConfig().propertyValueInt("grouperUi.import.progressRefreshSeconds");
871           progressRefreshSeconds = Math.max(progressRefreshSeconds, 1);
872           progressRefreshSeconds *= 1000;
873           guiResponseJs.addAction(GuiScreenAction.newScript("setTimeout(function() {ajax('../app/UiV2GroupImport.groupImportReportStatus?uniqueImportId=" + uniqueImportId + "')}, " + progressRefreshSeconds + ")"));
874         } else {
875           // it is complete, leave it be
876           importThreadProgress.put(reportMultiKey, null);
877         }
878       }
879     } catch (RuntimeException re) {
880       debugMap.put("exception", GrouperUtil.getFullStackTrace(re));
881       throw re;
882     } finally {
883       if (LOG.isDebugEnabled()) {
884         debugMap.put("tookMillis", (System.nanoTime()-startNanos)/1000000);
885         LOG.debug(GrouperUtil.mapToString(debugMap));
886       }
887     }
888 
889 
890   }
891 
892   /**
893    * method to do logic for import submit (note, dont use anything related to session here)
894    * @param loggedInSubject 
895    * @param groupImportContainer 
896    * @param groups 
897    * @param subjectSet 
898    * @param listInvalidSubjectIdsAndRow 
899    * @param removeMembers 
900    * @param importReplaceMembers 
901    * @param bulkAddOption 
902    * @param fileName 
903    * @param startDate
904    * @param endDate
905    */
906   private void groupImportSubmitHelper(final Subject loggedInSubject, final GroupImportContainer groupImportContainer, 
907       final Set<Group> groups, final Set<Subject> subjectSet, Map<String, Integer> listInvalidSubjectIdsAndRow, 
908       boolean removeMembers, boolean importReplaceMembers, String bulkAddOption, String fileName, List<CSVRecord> csvEntries,
909       Timestamp startDate, Timestamp endDate) {
910     
911     Map<String, Object> debugMap = new LinkedHashMap<String, Object>();
912     
913     debugMap.put("method", "groupImportSubmit");
914 
915     GrouperSession grouperSession = null;
916 
917     int pauseBetweenRecordsMillis = GrouperUiConfig.retrieveConfig().propertyValueIntRequired("grouperUi.import.pauseInBetweenRecordsMillis");
918         
919     try {
920       grouperSession = GrouperSession.start(loggedInSubject);
921 
922       ProgressBean progressBean = groupImportContainer.getProgressBean();
923       
924       if (GrouperUtil.length(subjectSet) == 0 && csvEntries != null) {
925         subjectSet.addAll(SimpleMembershipUpdateImportExport.parseCsvImportFile(csvEntries, new ArrayList<String>(), 
926             listInvalidSubjectIdsAndRow, true));
927       }
928       
929       Iterator<Group> groupIterator = groups.iterator();
930 
931       Set<GuiGroup> guiGroups = new LinkedHashSet<GuiGroup>();
932       groupImportContainer.setGuiGroups(guiGroups);
933       
934       progressBean.setProgressTotalRecords(GrouperUtil.length(groups) * GrouperUtil.length(subjectSet));
935       
936       //lets go through the groups that were submitted
937       while (groupIterator.hasNext()) {
938 
939         final Group group = groupIterator.next();
940 
941         guiGroups.add(new GuiGroup(group));
942 
943         GroupImportGroupSummarypImportGroupSummary.html#GroupImportGroupSummary">GroupImportGroupSummary groupImportGroupSummary = new GroupImportGroupSummary();
944         groupImportContainer.getGroupImportGroupSummaryForGroupMap().put(group, groupImportGroupSummary);
945         
946         List<Member> existingMembers = new ArrayList<Member>(GrouperUtil.nonNull(group.getImmediateMembers()));
947         List<Subject> subjectList = new ArrayList<Subject>(GrouperUtil.nonNull(subjectSet));
948         groupImportGroupSummary.setGroupCountOriginal(GrouperUtil.length(existingMembers));
949         
950         if (removeMembers) {
951           // if we're removing members, also include disabled memberships in the existingMembers list since we'll want to delete those as well
952           Set<Object[]> membershipsResult = MembershipFinder.findMemberships(GrouperUtil.toList(group.getId()), null, null, MembershipType.IMMEDIATE, Group.getDefaultList(), null, null, null, null, false);
953           for (Object[] membershipResult : membershipsResult) {
954             existingMembers.add((Member)membershipResult[2]);
955           }
956         }
957 
958         List<Member> overlappingMembers = new ArrayList<Member>(GrouperUtil.nonNull(GrouperUiUtils.removeOverlappingSubjects(existingMembers, subjectList)));
959 
960         // figure out subject not founds
961         if (listInvalidSubjectIdsAndRow.size() > 0) {
962           for (String subjectLabel : listInvalidSubjectIdsAndRow.keySet()) {
963             int rowNumber = listInvalidSubjectIdsAndRow.get(subjectLabel);
964             
965             GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(subjectLabel, TextContainer.retrieveFromRequest().getText().get(
966                 "groupImportProblemFindingSubjectError"), rowNumber);
967             
968             groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
969             
970             groupImportGroupSummary.groupCountErrorsIncrement();
971           }
972         }
973 
974         if (!removeMembers) {
975           progressBean.addProgressCompleteRecords(GrouperUtil.length(subjectSet) - GrouperUtil.length(subjectList));
976           //first lets add some members
977           for (int i=0;i<subjectList.size();i++) {
978             
979             Subject subject = subjectList.get(i);
980 
981             boolean hasError = false;
982             if (subject instanceof ImportSubjectWrapper) {
983               try {
984                 subject = ((ImportSubjectWrapper)subject).wrappedSubject();
985               } catch (Exception e) {
986                 int rowNumber = ((ImportSubjectWrapper)subject).getRow();
987                 String label = ImportSubjectWrapper.errorLabelForRowStatic(rowNumber, ((ImportSubjectWrapper)subject).getRowData());
988                 GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(label, TextContainer.retrieveFromRequest().getText().get(
989                     "groupImportProblemFindingSubjectError"), rowNumber);
990                 
991                 groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
992                 
993                 groupImportGroupSummary.groupCountErrorsIncrement();
994                 hasError = true;
995               }
996             }
997             
998             try {
999               // try this even if we have an error
1000               group.internal_addMember(subject, Group.getDefaultList(), false, null, startDate, endDate);
1001               GrouperUtil.sleep(pauseBetweenRecordsMillis);
1002               groupImportGroupSummary.groupCountAddedIncrement();
1003             } catch (Exception e) {
1004               if (!hasError) {
1005                 // if not already logged
1006                 String subjectString = SubjectUtils.subjectToString(subject);
1007 
1008                 GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(subjectString, GrouperUtil.xmlEscape(e.getMessage()));
1009                 groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
1010 
1011                 groupImportGroupSummary.groupCountErrorsIncrement();
1012                 LOG.warn("error with " + subjectString, e);
1013               }
1014             }
1015             progressBean.addProgressCompleteRecords(1);
1016       
1017           }
1018         } else {
1019           progressBean.addProgressCompleteRecords(GrouperUtil.length(subjectSet) - GrouperUtil.length(overlappingMembers));
1020           //first lets remove some members
1021           for (int i=0;i<overlappingMembers.size();i++) {
1022             
1023             Member member = overlappingMembers.get(i);
1024             
1025             try {
1026                 
1027               // note that this would delete disabled memberships too
1028               group.deleteMember(member, false);
1029               GrouperUtil.sleep(pauseBetweenRecordsMillis);
1030               
1031               groupImportGroupSummary.groupCountDeletedIncrement();
1032             } catch (Exception e) {
1033               String subjectString = SubjectUtils.subjectToString(member.getSubject());
1034               GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(subjectString, GrouperUtil.xmlEscape(e.getMessage()));
1035               groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
1036               groupImportGroupSummary.groupCountErrorsIncrement();
1037               LOG.warn("error with " + subjectString, e);
1038             }
1039 
1040             progressBean.addProgressCompleteRecords(1);
1041 
1042           }
1043           
1044         }
1045         
1046         if (!removeMembers && overlappingMembers.size() > 0) {
1047           // make sure start/end dates are correct
1048           Set<Membership> overlappingMemberships = group.getImmediateMemberships(Group.getDefaultList(), overlappingMembers);
1049           
1050           Map<String, Membership> overlappingMembershipsMap = new LinkedHashMap<String, Membership>();
1051           for (Membership overlappingMembership : overlappingMemberships) {
1052             overlappingMembershipsMap.put(overlappingMembership.getMemberUuid(), overlappingMembership);
1053           }
1054           
1055           // go through based on overlappingMembers instead of overlappingMemberships in case the latter has anything extra
1056           for (Member overlappingMember : overlappingMembers) {
1057             Membership overlappingMembership = overlappingMembershipsMap.get(overlappingMember.getUuid());
1058             if (overlappingMembership != null) {
1059               if (!GrouperUtil.equals(overlappingMembership.getEnabledTime(), startDate) || 
1060                   !GrouperUtil.equals(overlappingMembership.getDisabledTime(), endDate)) {
1061                 
1062                 try {
1063                   overlappingMembership.setEnabledTime(startDate);
1064                   overlappingMembership.setDisabledTime(endDate);
1065                   GrouperDAOFactory.getFactory().getMembership().update(overlappingMembership);
1066                   GrouperUtil.sleep(pauseBetweenRecordsMillis);
1067                   groupImportGroupSummary.groupCountUpdatedIncrement();
1068                 } catch (Exception e) {
1069                   String subjectString = SubjectUtils.subjectToString(overlappingMember.getSubject());
1070                   GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(subjectString, GrouperUtil.xmlEscape(e.getMessage()));
1071                   groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
1072                   groupImportGroupSummary.groupCountErrorsIncrement();
1073                   LOG.warn("error with " + subjectString, e);
1074                 }
1075               }
1076             }
1077           }
1078         }
1079     
1080         boolean didntImportDueToSubjects = groupImportGroupSummary.getGroupCountErrors() > 0;
1081     
1082         //remove the ones which are already there
1083         if (importReplaceMembers && !didntImportDueToSubjects && !removeMembers) {
1084           
1085           progressBean.addProgressCompleteRecords(GrouperUtil.length(subjectSet) - GrouperUtil.length(existingMembers));
1086           for (Member existingMember : existingMembers) {
1087             
1088             try {
1089               group.deleteMember(existingMember, false);
1090               GrouperUtil.sleep(pauseBetweenRecordsMillis);
1091               groupImportGroupSummary.groupCountDeletedIncrement();
1092             } catch (Exception e) {
1093 
1094               
1095               String subjectString = SubjectUtils.subjectToString(existingMember.getSubject());
1096               GroupImportError/beans/ui/GroupImportError.html#GroupImportError">GroupImportError groupImportError = new GroupImportError(subjectString, GrouperUtil.xmlEscape(e.getMessage()));
1097               groupImportGroupSummary.getGroupImportErrors().add(groupImportError);
1098               groupImportGroupSummary.groupCountErrorsIncrement();
1099               LOG.warn("error with " + subjectString, e);
1100 
1101             }
1102             progressBean.addProgressCompleteRecords(1);
1103           }
1104         }
1105 
1106         //this might be a little wasteful, but I think it is a good sanity check
1107         int newSize = group.getImmediateMembers().size();
1108 
1109         groupImportGroupSummary.setGroupCountNew(newSize);
1110         
1111         try {
1112           GrouperUserDataApi.recentlyUsedGroupAdd(GrouperUiUserData.grouperUiGroupNameForUserData(), 
1113               loggedInSubject, group);
1114         } catch (Exception e) {
1115           LOG.warn("Cant add recently used group: " + group.getName() + ", for subject: " + SubjectUtils.subjectToString(loggedInSubject) + ", maybe a priv was lost after import started???", e);
1116         }
1117         
1118         if (StringUtils.equals(bulkAddOption, "import")) {
1119           auditImport(group.getUuid(), group.getName(), fileName, groupImportGroupSummary.getGroupCountAdded(), groupImportGroupSummary.getGroupCountDeleted(), groupImportGroupSummary.getGroupCountUpdated());
1120         }
1121 
1122         groupImportGroupSummary.setComplete(true);
1123       }
1124       // done
1125       progressBean.setProgressCompleteRecords(progressBean.getProgressTotalRecords());
1126       
1127 
1128     } catch (NoSessionException se) {
1129       throw se;
1130     } catch (ControllerDone cd) {
1131       throw cd;
1132     } finally {
1133       GrouperSession.stopQuietly(grouperSession);
1134     }
1135   }
1136   
1137     private void auditImport(final String groupId, final String groupName, final String fileName,
1138         final int countAdded, final int countDeleted, final int countUpdated) {
1139       HibernateSession.callbackHibernateSession(
1140 		    GrouperTransactionType.READ_WRITE_OR_USE_EXISTING, AuditControl.WILL_AUDIT,
1141 			    new HibernateHandler() {
1142 				    public Object callback(HibernateHandlerBean hibernateHandlerBean)
1143 					    throws GrouperDAOException {
1144 						  
1145 				      AuditEntry auditEntry = new AuditEntry(AuditTypeBuiltin.MEMBERSHIP_GROUP_IMPORT, "file", fileName, "totalAdded", 
1146 				          String.valueOf(countAdded), "groupId", groupId, "groupName", groupName, "totalDeleted", String.valueOf(countDeleted));
1147 						  
1148 				      String description = "Added : " + countAdded + " subjects, updated " + countUpdated + " subjects"
1149 				          + " and deleted "+countDeleted + " subjects in group "+groupName;
1150 						auditEntry.setDescription(description);
1151 						auditEntry.saveOrUpdate(true);
1152 						  
1153 						return null;
1154 					  }
1155       });
1156     }
1157   
1158   
1159   /**
1160    * modal search form results for add group to import
1161    * @param request
1162    * @param response
1163    */
1164   public void groupImportGroupSearch(HttpServletRequest request, HttpServletResponse response) {
1165     
1166     GrouperRequestContainer grouperRequestContainer = GrouperRequestContainer.retrieveFromRequestOrCreate();
1167     
1168     GroupContainer groupContainer = grouperRequestContainer.getGroupContainer();
1169     
1170     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1171     GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1172   
1173     GrouperSession grouperSession = null;
1174     
1175     try {
1176       grouperSession = GrouperSession.start(loggedInSubject);
1177   
1178       String searchString = request.getParameter("addGroupSearch");
1179       
1180       boolean searchOk = GrouperUiUtils.searchStringValid(searchString);
1181       if (!searchOk) {
1182         
1183         guiResponseJs.addAction(GuiScreenAction.newInnerHtml("#addGroupResults", 
1184             TextContainer.retrieveFromRequest().getText().get("groupImportAddToGroupNotEnoughChars")));
1185         return;
1186       }
1187 
1188       String matchExactIdString = request.getParameter("matchExactId[]");
1189       boolean matchExactId = GrouperUtil.booleanValue(matchExactIdString, false);
1190 
1191       GuiPaging guiPaging = groupContainer.getGuiPaging();
1192       QueryOptions queryOptions = new QueryOptions();
1193 
1194       GrouperPagingTag2.processRequest(request, guiPaging, queryOptions); 
1195 
1196       Set<Group> groups = null;
1197     
1198     
1199       GroupFinder groupFinder = new GroupFinder().assignPrivileges(AccessPrivilege.UPDATE_PRIVILEGES)
1200         .assignScope(searchString).assignSplitScope(true).assignQueryOptions(queryOptions);
1201       
1202       if (matchExactId) {
1203         groupFinder.assignFindByUuidOrName(true);
1204       }
1205       
1206       groups = groupFinder.findGroups();
1207       
1208       guiPaging.setTotalRecordCount(queryOptions.getQueryPaging().getTotalRecordCount());
1209       
1210       if (GrouperUtil.length(groups) == 0) {
1211 
1212         guiResponseJs.addAction(GuiScreenAction.newInnerHtml("#addGroupResults", 
1213             TextContainer.retrieveFromRequest().getText().get("groupImportAddGroupNotFound")));
1214         return;
1215       }
1216       
1217       Set<GuiGroup> guiGroups = GuiGroup.convertFromGroups(groups);
1218       
1219       groupContainer.setGuiGroups(guiGroups);
1220   
1221       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#addGroupResults", 
1222           "/WEB-INF/grouperUi2/groupImport/groupImportAddGroupResults.jsp"));
1223       
1224     } finally {
1225       GrouperSession.stopQuietly(grouperSession);
1226     }
1227     
1228   }
1229 
1230   
1231   /**
1232    * import group members screen remove group from list
1233    * @param request
1234    * @param response
1235    */
1236   public void groupImportRemoveGroup(HttpServletRequest request, HttpServletResponse response) {
1237     
1238     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1239     
1240     GrouperSession grouperSession = null;
1241   
1242     try {
1243       
1244       grouperSession = GrouperSession.start(loggedInSubject);
1245 
1246       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1247 
1248       groupImportSetupExtraGroups(loggedInSubject, request, guiResponseJs, true, false, new LinkedHashSet<Group>(), false);
1249       
1250       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#groupImportExtraGroupsDivId", 
1251           "/WEB-INF/grouperUi2/groupImport/groupImportExtraGroups.jsp"));
1252 
1253     } finally {
1254       GrouperSession.stopQuietly(grouperSession);
1255     }
1256   }
1257 
1258   /**
1259    * import group members screen remove subject from list
1260    * @param request
1261    * @param response
1262    */
1263   public void groupImportRemoveSubject(HttpServletRequest request, HttpServletResponse response) {
1264     
1265     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1266     
1267     GrouperSession grouperSession = null;
1268   
1269     try {
1270       
1271       grouperSession = GrouperSession.start(loggedInSubject);
1272 
1273       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1274 
1275       groupImportSetupExtraSubjects(loggedInSubject, request, guiResponseJs, true, false, new LinkedHashSet<Subject>(), false);
1276       
1277       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#groupImportExtraMembersDivId", 
1278           "/WEB-INF/grouperUi2/groupImport/groupImportExtraSubjects.jsp"));
1279       
1280     } finally {
1281       GrouperSession.stopQuietly(grouperSession);
1282     }
1283   }
1284 
1285   /**
1286    * import group members screen add group to list
1287    * @param request
1288    * @param response
1289    */
1290   public void groupImportAddGroup(HttpServletRequest request, HttpServletResponse response) {
1291     
1292     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1293     
1294     GrouperSession grouperSession = null;
1295   
1296     try {
1297       
1298       grouperSession = GrouperSession.start(loggedInSubject);
1299 
1300       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1301 
1302       groupImportSetupExtraGroups(loggedInSubject, request, guiResponseJs, false, true, new LinkedHashSet<Group>(), true);
1303 
1304       //clear out combobox
1305       guiResponseJs.addAction(GuiScreenAction.newScript(
1306           "dijit.byId('groupImportGroupComboId').set('displayedValue', ''); " +
1307           "dijit.byId('groupImportGroupComboId').set('value', '');"));
1308 
1309       
1310       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#groupImportExtraGroupsDivId", 
1311           "/WEB-INF/grouperUi2/groupImport/groupImportExtraGroups.jsp"));
1312 
1313     } finally {
1314       GrouperSession.stopQuietly(grouperSession);
1315     }
1316   }
1317 
1318   /**
1319    * import group members screen
1320    * @param request
1321    * @param response
1322    */
1323   public void groupImport(HttpServletRequest request, HttpServletResponse response) {
1324     
1325     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1326     
1327     GrouperSession grouperSession = null;
1328   
1329     try {
1330   
1331       grouperSession = GrouperSession.start(loggedInSubject);
1332 
1333       GrouperRequestContainerperRequestContainer.html#GrouperRequestContainer">GrouperRequestContainer grouperRequestContainer = new GrouperRequestContainer();
1334       GroupImportContainer groupImportContainer = grouperRequestContainer.getGroupImportContainer();
1335       
1336       String backTo = request.getParameter("backTo");
1337       
1338       {
1339         //this will also put the group in the group container so it can populate the combobox
1340         Group group = UiV2Group.retrieveGroupHelper(request, AccessPrivilege.UPDATE, false).getGroup();
1341         if (group != null && StringUtils.equals("group", backTo)) {
1342           groupImportContainer.setImportFromGroup(true);
1343         }
1344       }
1345       {
1346         Subject subject = UiV2Subject.retrieveSubjectHelper(request, false);
1347         if (subject != null && StringUtils.equals("subject", backTo)) {
1348           groupImportContainer.setImportFromSubject(true);
1349         }
1350       }
1351 
1352       
1353       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1354       
1355       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#grouperMainContentDivId", 
1356           "/WEB-INF/grouperUi2/groupImport/groupImport.jsp"));
1357   
1358     } finally {
1359       GrouperSession.stopQuietly(grouperSession);
1360     }
1361   }
1362 
1363   /**
1364    * import group members screen add member to list
1365    * @param request
1366    * @param response
1367    */
1368   public void groupImportAddMember(HttpServletRequest request, HttpServletResponse response) {
1369     
1370     final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();
1371     
1372     GrouperSession grouperSession = null;
1373   
1374     try {
1375       
1376       grouperSession = GrouperSession.start(loggedInSubject);
1377   
1378       GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();
1379   
1380       groupImportSetupExtraSubjects(loggedInSubject, request, guiResponseJs, false, true, new LinkedHashSet<Subject>(), true);
1381   
1382       //clear out combobox
1383       guiResponseJs.addAction(GuiScreenAction.newScript(
1384           "dijit.byId('groupAddMemberComboId').set('displayedValue', ''); " +
1385           "dijit.byId('groupAddMemberComboId').set('value', '');"));
1386   
1387       
1388       guiResponseJs.addAction(GuiScreenAction.newInnerHtmlFromJsp("#groupImportExtraMembersDivId", 
1389           "/WEB-INF/grouperUi2/groupImport/groupImportExtraSubjects.jsp"));
1390   
1391     } finally {
1392       GrouperSession.stopQuietly(grouperSession);
1393     }
1394   }
1395 
1396   /**
1397    * setup the extra members (other than combobox), and maybe move the combobox down
1398    * @param loggedInSubject
1399    * @param request
1400    * @param considerRemoveSubjectSourceAndId if removing one
1401    * @param includeCombobox
1402    * @param allSubjects is a LinkedHashSet of subjects
1403    * @param errorOnNullCombobox is true if an error should appear if there is nothing in the combobox
1404    * @return true if ok, false if not
1405    */
1406   private boolean groupImportSetupExtraSubjects(Subject loggedInSubject, 
1407       HttpServletRequest request, GuiResponseJs guiResponseJs, boolean considerRemoveSubjectSourceAndId, 
1408       boolean includeCombobox, Set<Subject> allSubjects, boolean errorOnNullCombobox) {
1409 
1410     //extra source ids and subjects ids
1411     Set<MultiKey> extraSubjectSourceAndIds = new HashSet<MultiKey>();
1412     Set<GuiSubject> extraGuiSubjects = new LinkedHashSet<GuiSubject>();
1413     
1414     GrouperRequestContainer.retrieveFromRequestOrCreate().getGroupImportContainer().setGroupImportExtraGuiSubjects(extraGuiSubjects);
1415     
1416     Set<MultiKey> allSubjectsSourceAndIds = new HashSet<MultiKey>();
1417   
1418     String removeSubjectSourceAndId = null;
1419   
1420     //if removing a group id
1421     if (considerRemoveSubjectSourceAndId) {
1422       removeSubjectSourceAndId = request.getParameter("removeSubjectSourceAndId");
1423       if (StringUtils.isBlank(removeSubjectSourceAndId)) {
1424         throw new RuntimeException("Why would removeSubjectSourceAndId be empty????");
1425       }
1426     }
1427 
1428     Subject theSubject = null;
1429     
1430     {
1431       //if moving combobox down to extra list or getting all groups
1432       String comboValue = request.getParameter("groupAddMemberComboName");
1433       
1434       if (StringUtils.isBlank(comboValue)) {
1435         //if didnt pick one from results
1436         comboValue = request.getParameter("groupAddMemberComboDisplay");
1437       }
1438       
1439       try {
1440         GrouperSourceAdapter.searchForGroupsWithReadPrivilege(true);
1441         if (comboValue != null && comboValue.contains("||")) {
1442           String sourceId = GrouperUtil.prefixOrSuffix(comboValue, "||", true);
1443           String subjectId = GrouperUtil.prefixOrSuffix(comboValue, "||", false);
1444           theSubject =  SubjectFinder.findByIdOrIdentifierAndSource(subjectId, sourceId, false);
1445         } else {
1446           try {
1447             theSubject = StringUtils.isBlank(comboValue) ? null : SubjectFinder.findByIdOrIdentifier(comboValue, false);
1448           } catch (SubjectNotUniqueException snue) {
1449             //ignore
1450           }
1451         }
1452       } finally {
1453         GrouperSourceAdapter.clearSearchForGroupsWithReadPrivilege();
1454       }
1455     }
1456     boolean success = true;
1457     if (theSubject == null) {
1458       if (includeCombobox && errorOnNullCombobox) {
1459         guiResponseJs.addAction(GuiScreenAction.newValidationMessage(GuiMessageType.error, 
1460             "#groupAddMemberComboErrorId", 
1461             TextContainer.retrieveFromRequest().getText().get("groupImportSubjectNotFound")));
1462         success = false;
1463       }
1464       
1465     } else {
1466       MultiKey multiKey = new MultiKey(theSubject.getSourceId(), theSubject.getId());
1467       if (includeCombobox) {
1468         if (!extraSubjectSourceAndIds.contains(multiKey)) {
1469           extraGuiSubjects.add(new GuiSubject(theSubject));
1470           extraSubjectSourceAndIds.add(multiKey);
1471         }
1472       }
1473       //always add to all groups
1474       if (!allSubjectsSourceAndIds.contains(multiKey)) {
1475         allSubjects.add(theSubject);
1476         allSubjectsSourceAndIds.add(multiKey);
1477       }
1478     }
1479   
1480     //loop through all the hidden fields (max 100)
1481     for (int i=0;i<100;i++) {
1482       String extraSourceIdSubjectId = request.getParameter("extraSourceIdSubjectId_" + i);
1483       
1484       //we are at the end
1485       if (StringUtils.isBlank(extraSourceIdSubjectId)) {
1486         break;
1487       }
1488       
1489       //might be removing this one
1490       if (considerRemoveSubjectSourceAndId && StringUtils.equals(removeSubjectSourceAndId, extraSourceIdSubjectId)) {
1491         continue;
1492       }
1493       
1494       theSubject = null;
1495 
1496       try {
1497         GrouperSourceAdapter.searchForGroupsWithReadPrivilege(true);
1498         if (extraSourceIdSubjectId != null && extraSourceIdSubjectId.contains("||")) {
1499           String sourceId = GrouperUtil.prefixOrSuffix(extraSourceIdSubjectId, "||", true);
1500           String subjectId = GrouperUtil.prefixOrSuffix(extraSourceIdSubjectId, "||", false);
1501           theSubject =  SubjectFinder.findByIdOrIdentifierAndSource(subjectId, sourceId, false);
1502         }
1503       } finally {
1504         GrouperSourceAdapter.clearSearchForGroupsWithReadPrivilege();
1505       }
1506       
1507       if (theSubject != null) {
1508         MultiKey multiKey = new MultiKey(theSubject.getSourceId(), theSubject.getId());
1509         if (!extraSubjectSourceAndIds.contains(multiKey)) {
1510           extraGuiSubjects.add(new GuiSubject(theSubject));
1511           extraSubjectSourceAndIds.add(multiKey);
1512         }
1513   
1514         if (!allSubjectsSourceAndIds.contains(multiKey)) {
1515           allSubjects.add(theSubject);
1516           allSubjectsSourceAndIds.add(multiKey);
1517         }
1518       }      
1519     }
1520     return success;
1521   }
1522 
1523 }