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}