001/**
002 * Copyright 2011-2013 The Kuali Foundation Licensed under the Educational Community
003 * License, Version 2.0 (the "License"); you may not use this file except in
004 * compliance with the License. You may obtain a copy of the License at
005 *
006 * http://www.osedu.org/licenses/ECL-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
010 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
011 * License for the specific language governing permissions and limitations under
012 * the License.
013 */
014package org.kuali.mobility.people.controllers;
015
016import java.io.ByteArrayOutputStream;
017import java.util.ArrayList;
018import java.util.HashMap;
019import java.util.LinkedHashMap;
020import java.util.List;
021import java.util.Locale;
022import java.util.Map;
023import java.util.Properties;
024
025import javax.annotation.Resource;
026import javax.servlet.ServletOutputStream;
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029import javax.servlet.http.HttpSession;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032import org.kuali.mobility.campus.entity.Campus;
033import org.kuali.mobility.campus.service.CampusService;
034import org.kuali.mobility.people.entity.Group;
035import org.kuali.mobility.people.entity.Person;
036import org.kuali.mobility.people.entity.SearchCriteria;
037import org.kuali.mobility.people.entity.SearchResult;
038import org.kuali.mobility.people.service.DirectoryService;
039import org.springframework.beans.factory.annotation.Autowired;
040import org.springframework.beans.factory.annotation.Qualifier;
041import org.springframework.context.MessageSource;
042import org.springframework.stereotype.Controller;
043import org.springframework.ui.Model;
044import org.springframework.validation.BindingResult;
045import org.springframework.validation.Errors;
046import org.springframework.web.bind.annotation.ModelAttribute;
047import org.springframework.web.bind.annotation.PathVariable;
048import org.springframework.web.bind.annotation.RequestMapping;
049import org.springframework.web.bind.annotation.RequestMethod;
050import org.springframework.web.servlet.LocaleResolver;
051
052/**
053 * Controller for the people tool
054 * @author Kuali Mobility Team (mobility.collab@kuali.org)
055 * @since
056 */
057@Controller
058@RequestMapping("/people")
059public class PeopleController {
060
061        /** A reference to a logger */
062        private static final Logger LOG = LoggerFactory.getLogger(PeopleController.class);
063        
064        private static final String PEOPLE_SEARCH_RESULT = "People.Search.Results";
065        private static final String PEOPLE_SEARCH_UNIQUE_RESULT = "People.Search.UniqueResult";
066        private static final String PEOPLE_USERNAME_HASHES = "People.UserNames.Hashes";
067        private static final String PEOPLE_SEARCH_RESULTS_PERSON = "People.Search.Results.Person";
068        private static final String PEOPLE_SEARCH_CRITERIA = "People.Search.Criteria";
069        private static final String GROUP_DISTINGUISHED_NAME_HASHES = "Group.distinguishedName.Hashes";
070        private static final String GROUP_SEARCH_RESULTS = "Group.Search.Results";
071        private static final String GROUP_DETAILS_MEMBERS = "Group.Details.Members";
072        private static final String GROUP_SEARCH_RESULTS_GROUP = "Group.Search.Results.Group";
073        
074        /**
075         * Map of status types
076         */
077        private static final Map<String, String> statusTypes = new LinkedHashMap<String, String>();
078        
079        static{
080                statusTypes.put("Any", "Any Status");
081                statusTypes.put("Student", "Student");
082                statusTypes.put("Faculty", "Faculty");
083                statusTypes.put("Employee", "Employee");
084        }
085        
086        /**
087         * A reference to the <code>DirectoryService</code>
088         */
089        @Autowired
090        @Qualifier("directoryService")
091        private DirectoryService peopleService;
092
093        /**
094         * A reference to the Directory properties
095         */
096        @Autowired
097        @Qualifier("directoryProperties")
098        private Properties directoryProperties;
099        
100        /**
101         * A reference to the <code>CampusService</code>.
102         */
103        @Autowired
104        private CampusService campusService;
105        
106        @Resource(name="kmeProperties")
107    private Properties kmeProperties;
108        
109        /**
110         * A reference to the Spring Message source
111         */
112        @Resource(name="messageSource")
113        private MessageSource messageSource;
114        
115        /**
116         * A reference to the Spring Locale Resolver
117         */
118        @Resource(name="localeResolver")
119        private LocaleResolver localeResolver;
120
121        /**
122         * Default page
123         */
124        @RequestMapping(method = RequestMethod.GET)
125        public String index(Model uiModel, HttpServletRequest request) {
126                String viewName = null;
127                SearchCriteria s = new SearchCriteria();
128                uiModel.addAttribute("search", s);
129                // removeFromCache(request.getSession(), PEOPLE_SEARCH_RESULT);
130                // request.setAttribute("watermark",
131                // "[Keyword] or [Last, First] or [First Last]");
132                uiModel.addAttribute("locations", getCampusNames());
133                uiModel.addAttribute("statusTypes", getStatusTypes(request));
134
135                uiModel.addAttribute("basicSearchDiv", "div1");
136                uiModel.addAttribute("advancedSearchDiv", "div2");
137
138                uiModel.addAttribute("enableAdvancedSearchToggle", isAdvancedSearchEnabled());
139                if ("3".equalsIgnoreCase(getKmeProperties().getProperty("kme.uiVersion","classic"))) {
140                        //viewName = "people/index";
141                        viewName = "ui3/people/index";
142                } else {
143                        viewName = "people/index";
144                }
145                return viewName;
146        }
147        
148        @RequestMapping(method = RequestMethod.POST)
149        public String postSimpleSearch(Model uiModel,
150                        @ModelAttribute("search") SearchCriteria search,
151                        BindingResult result, 
152                        HttpServletRequest request) {
153                
154                uiModel.addAttribute("locations", getCampusNames());
155                uiModel.addAttribute("statusTypes", getStatusTypes(request));
156
157                String searchTypeString = request.getParameter("searchtype");
158                if (searchTypeString.equals("BasicSearch")) {
159                        uiModel.addAttribute("basicSearchDiv", "div1");
160                        uiModel.addAttribute("advancedSearchDiv", "div2");
161                }
162                if (searchTypeString.equals("AdvancedSearch")) {
163                        uiModel.addAttribute("advancedSearchDiv", "div1");
164                        uiModel.addAttribute("basicSearchDiv", "div2");
165                }
166                if (validateSimpleSearch(search, result, request)) {
167                        SearchResult results = getPeopleService().findEntries(search);
168                        // LOG.debug("people POST size: " + people.size());
169                        if (results != null) {
170                                Map<String, String> userNameHashes = new HashMap<String, String>();
171                                removeFromCache(request.getSession(), PEOPLE_SEARCH_UNIQUE_RESULT);
172                                for (Person p : results.getPeople()) {
173                                        if (p.getUserName() != null) {
174                                                // LOG.debug("p.getHashedUserName() " +
175                                                // p.getHashedUserName());
176                                                userNameHashes.put(p.getHashedUserName(),p.getUserName());
177                                        }
178                                        if (p.getUserName() != null
179                                                        && search != null
180                                                        && search.getSearchText() != null
181                                                        && p.getUserName().trim().equals(search.getSearchText().trim())) {
182                                                putInCache(request.getSession(), PEOPLE_SEARCH_UNIQUE_RESULT, p);
183                                        }
184                                }
185
186                                request.getSession().setAttribute(PEOPLE_USERNAME_HASHES, userNameHashes);
187                                putInCache(request.getSession(), PEOPLE_SEARCH_RESULT, results.getPeople());
188                                putInCache(request.getSession(), PEOPLE_SEARCH_CRITERIA, search);
189
190                                Map<String, String> groupDNHashes = new HashMap<String, String>();
191                                for (Group g : results.getGroups()) {
192                                        if (g.getDisplayName() != null) {
193                                                LOG.debug("g.getHashedDN() " + g.getHashedDN());
194                                                groupDNHashes.put(g.getHashedDN(), g.getDN());
195                                        }
196                                }
197                                request.getSession().setAttribute(GROUP_DISTINGUISHED_NAME_HASHES, groupDNHashes);
198                                // LOG.debug("group POST size: " + group.size());
199                                putInCache(request.getSession(), GROUP_SEARCH_RESULTS, results.getGroups());
200                                request.setAttribute("watermark", getLocalisedMessage("people.searchWatermark", request));
201                        }
202                        return "people/index";
203                } else {
204                        return "people/index";
205                }
206        }
207
208        @RequestMapping(value = "/advanced", method = RequestMethod.GET)
209        public String viewSearchForm(Model uiModel, HttpServletRequest request) {
210                SearchCriteria s = new SearchCriteria();
211                s.setStatus("Any");
212                uiModel.addAttribute("search", s);
213                uiModel.addAttribute("locations", getCampusNames());
214                uiModel.addAttribute("statusTypes", getStatusTypes(request));
215
216                return "people/form";
217        }
218
219        @RequestMapping(value = "/advanced", method = RequestMethod.POST)
220        public String postSearchForm(Model uiModel,
221                        @ModelAttribute("search") SearchCriteria search,
222                        BindingResult result, 
223                        HttpServletRequest request) {
224                
225                if (validateAdvancedSearch(search, result, request)) {
226                        SearchResult results = getPeopleService().findEntries(search);
227
228                        Map<String, String> userNameHashes = new HashMap<String, String>();
229                        for (Person p : results.getPeople()) {
230                                userNameHashes.put(p.getHashedUserName(), p.getUserName());
231                        }
232                        request.getSession().setAttribute(PEOPLE_USERNAME_HASHES,userNameHashes);
233                        putInCache(request.getSession(), PEOPLE_SEARCH_RESULT,results.getPeople());
234
235                        return "people/list";
236                } else {
237                        uiModel.addAttribute("statusTypes",  getStatusTypes(request));
238                        uiModel.addAttribute("locations", getCampusNames());
239                        return "people/form";
240                }
241        }
242
243        @RequestMapping(value = "/{userNameHash}", method = RequestMethod.GET)
244        public String getUserDetails(Model uiModel, 
245                        HttpServletRequest request,
246                        @PathVariable("userNameHash") String userNameHash) {
247
248                Map<String, Object> details = new HashMap<String, Object>();
249                Person p = getPeopleService().personLookup(userNameHash);
250                details.put("person", p);
251                details.put("loggedIn", false);
252                putInCache(request.getSession(), PEOPLE_SEARCH_RESULTS_PERSON,details);
253                uiModel.addAttribute("person", p);
254                return "people/details";
255        }
256
257        @SuppressWarnings("unchecked")
258        @RequestMapping(value = "/group/{hashedDN}", method = RequestMethod.GET)
259        public String getGroupDetails(Model uiModel, 
260                        HttpServletRequest request,
261                        @PathVariable("hashedDN") String hashedDisplayName) {
262
263                Map<String, Object> details = new HashMap<String, Object>();
264
265                // Map<String, String> GroupNameHashes = (Map<String, String>)
266                // request.getSession().getAttribute(GROUP_DISTINGUISHED_NAME_HASHES);
267                Group g = null;
268                // if (hashedDisplayName != null) {
269                // String groupName = GroupNameHashes.get(hashedDisplayName);
270                // LOG.debug("group:" + hashedDisplayName + "groupname:" + groupName);
271                String groupName = hashedDisplayName;
272                g = getPeopleService().groupLookup(groupName);
273                /*
274                 * if(g != null) LOG.debug("group detail " + g.getDisplayName());
275                 */
276                // }
277                details.put("group", g);
278                details.put("loggedIn", false);
279
280                uiModel.addAttribute("group", g);
281                uiModel.addAttribute("groupName", groupName);
282                putInCache(request.getSession(), GROUP_SEARCH_RESULTS_GROUP, details);
283
284                return "people/groupdetails";
285        }
286
287        /**
288         * adding groupmembers and owners page begin
289         */
290        @SuppressWarnings("unchecked")
291        @RequestMapping(value = "/group/groupmembers/{hashedDN}", method = RequestMethod.GET)
292        public String getGroupMembers(Model uiModel, HttpServletRequest request,
293                        @PathVariable("hashedDN") String hashedDisplayName) {
294                Map<String, Object> members = new HashMap<String, Object>();
295                // Map<String, String> GroupNameHashes = (Map<String, String>)
296                // request.getSession().getAttribute(GROUP_DISTINGUISHED_NAME_HASHES);
297                Group g = null;
298                // if (hashedDisplayName != null) {
299                // String groupName = GroupNameHashes.get(hashedDisplayName);
300                // LOG.debug("group:" + hashedDisplayName + "groupname:" + groupName);
301                String groupName = hashedDisplayName;
302                g = getPeopleService().groupLookup(groupName);
303                /*
304                 * if(g != null) LOG.debug("group detail " + g.getDiplayName());
305                 */
306                // }
307                members.put("group", g);
308                members.put("loggedIn", false);
309                uiModel.addAttribute("group", g);
310                uiModel.addAttribute("groupName", groupName);
311                putInCache(request.getSession(), GROUP_DETAILS_MEMBERS, members);
312                return "people/groupmembers";
313        }
314
315        /**
316         * adding groupmembers and owners page end
317         */
318        @RequestMapping(value = "/image/{hash}", method = RequestMethod.GET)
319        public void getImage(@PathVariable("hash") String imageKeyHash,
320                        Model uiModel, HttpServletRequest request,
321                        HttpServletResponse response) throws Exception {
322                byte[] byteArray = (byte[]) request.getSession().getAttribute(
323                                "People.Image.Email." + imageKeyHash);
324                if (byteArray != null) {
325                        ByteArrayOutputStream baos = new ByteArrayOutputStream(
326                                        byteArray.length);
327                        baos.write(byteArray);
328                        if (baos != null) {
329                                ServletOutputStream sos = null;
330                                try {
331                                        response.setContentLength(baos.size());
332                                        sos = response.getOutputStream();
333                                        baos.writeTo(sos);
334                                        sos.flush();
335                                } catch (Exception e) {
336                                        LOG.error("error creating image file", e);
337                                } finally {
338                                        try {
339                                                baos.close();
340                                                sos.close();
341                                        } catch (Exception e1) {
342                                                LOG.error("error closing output stream", e1);
343                                        }
344                                }
345                        }
346                }
347        }
348        
349        /**
350         * beginning of ui3
351         */
352        @RequestMapping(value="/templates/{key}")
353    public String getAngularTemplates(
354            @PathVariable("key") String key,
355            HttpServletRequest request,
356            Model uiModel ) {
357        return "ui3/people/templates/"+key;
358    }
359        
360        @RequestMapping(value = "/js/people.js")
361    public String getJavaScript(Model uiModel) {
362                uiModel.addAttribute("locations", getCampusNames());
363                uiModel.addAttribute("enableAdvancedSearchToggle", isAdvancedSearchEnabled());
364        return "ui3/people/js/people";
365    }
366        
367        /**
368         * end of ui3
369         */
370
371        /**
372         * Validates a simple search's seach criteria
373         * @param search
374         * @param result
375         * @param request
376         * @return
377         */
378        private boolean validateSimpleSearch(SearchCriteria search, BindingResult result, HttpServletRequest request) {
379                boolean hasErrors = false;
380                // Errors errors = ((Errors) result);
381                if ((search.getSearchText() == null || search.getSearchText().trim().isEmpty())) {
382                        // errors.rejectValue("searchText", "",
383                        // "You must provide at least one letter to search.");
384                        request.setAttribute("watermark", getLocalisedMessage("people.simpleSearchErrorWatermark", request));
385                        hasErrors = true;
386                }
387                return !hasErrors;
388        }
389
390        /**
391         * Validates an advanced searche's criteria
392         * @param search
393         * @param result
394         * @return
395         */
396        private boolean validateAdvancedSearch(SearchCriteria search, BindingResult result,HttpServletRequest request) {
397                boolean hasErrors = false;
398                Errors errors = ((Errors) result);
399                if ((search.getLastName() == null || search.getLastName().trim().isEmpty())
400                        && (search.getFirstName() == null || search.getFirstName().trim().isEmpty())
401                        && (search.getUserName() == null || search.getUserName().trim().isEmpty())) {
402                        errors.rejectValue(
403                                        "lastName",
404                                        "",
405                                        getLocalisedMessage("people.advancedSearchError", request));
406                        hasErrors = true;
407                }
408                return !hasErrors;
409        }
410
411        /**
412         * Sets the reference to the <code>DirectoryService<code>.
413         * @param peopleService Rreference to the <code>DirectoryService<code>.
414         */
415        public void setPeopleService(DirectoryService peopleService) {
416                this.peopleService = peopleService;
417        }
418
419        /**
420         * Puts an object in the user's session cache
421         * @param session
422         * @param key
423         * @param item
424         */
425        private void putInCache(HttpSession session, String key, Object item) {
426                session.setAttribute(key, item);
427        }
428
429        /**
430         * Gets an object from the user's session cache.
431         * @param session
432         * @param key
433         * @return
434         */
435        private Object getFromCache(HttpSession session, String key) {
436                return session.getAttribute(key);
437        }
438
439        /**
440         * Removes an object from the user's session cache
441         * @param session
442         * @param key
443         */
444        private void removeFromCache(HttpSession session, String key) {
445                session.removeAttribute(key);
446        }
447        
448        /**
449         * Gets a list of a available campus names
450         * @return
451         */
452        private List<String> getCampusNames(){
453                List<Campus> campusses = this.campusService.getCampuses();
454                List<String> names = new ArrayList<String>(campusses.size());
455                for(Campus campus : campusses){
456                        names.add(campus.getName());
457                }
458                return names;
459        }
460
461        /**
462         * Gets a map of session types
463         * @return
464         */
465        private Map<String, String> getStatusTypes(HttpServletRequest request) {
466                Map<String, String> statusNames = new LinkedHashMap<String, String>();
467                statusNames.put("Any", getLocalisedMessage("people.status.any", request));
468                statusNames.put("Student", getLocalisedMessage("people.status.student", request));
469                statusNames.put("Faculty", getLocalisedMessage("people.status.faculty", request));
470                statusNames.put("Employee", getLocalisedMessage("people.status.employee", request));
471                return statusNames;
472        }
473
474        /**
475         * Gets the reference to the Directory Service's properties
476         * @return the directoryProperties Reference to the Directory Service's properties
477         */
478        public Properties getDirectoryProperties() {
479                return directoryProperties;
480        }
481
482        /**
483         * Sets the reference to the Directory Service's properties.
484         * @param directoryProperties the directoryProperties to set
485         */
486        public void setDirectoryProperties(Properties directoryProperties) {
487                this.directoryProperties = directoryProperties;
488        }
489        
490        /**
491         * Sets the reference to the <code>CampusService</code>.
492         * @param campusService Reference to the <code>CampusService</code>.
493         */
494        public void setCampusService(CampusService campusService){
495                this.campusService = campusService;
496        }
497
498        /**
499         * Gets the reference to the <code>DirectoryService</code>.
500         * @return the peopleService Reference to the <code>DirectoryService</code>.
501         */
502        public DirectoryService getPeopleService() {
503                return peopleService;
504        }
505        
506        /**
507         * Returns true if advanced search is enabled
508         * @return True if the advanced search option should be available
509         */
510        private boolean isAdvancedSearchEnabled(){
511                return Boolean.parseBoolean(getDirectoryProperties().getProperty("people.enableAdvancedSearchToggle", "true"));
512        }
513        
514        /**
515         * Gets a localised message
516         * @param key Key of the message to get
517         * @param request The http request for the current user to render for
518         * @param args Arguments for the message
519         * @return A localised string
520         */
521        private String getLocalisedMessage(String key, HttpServletRequest request, String...args){
522                Locale locale = this.localeResolver.resolveLocale(request);
523                return messageSource.getMessage(key, args, locale);
524        }
525
526        public Properties getKmeProperties() {
527                return kmeProperties;
528        }
529
530        public void setKmeProperties(Properties kmeProperties) {
531                this.kmeProperties = kmeProperties;
532        }
533}