001/**
002 * Copyright 2011 The Kuali Foundation Licensed under the
003 * Educational Community License, Version 2.0 (the "License"); you may
004 * not use this file except in compliance with the License. You may
005 * obtain a copy of the License at
006 *
007 * http://www.osedu.org/licenses/ECL-2.0
008 *
009 * Unless required by applicable law or agreed to in writing,
010 * software distributed under the License is distributed on an "AS IS"
011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012 * or implied. See the License for the specific language governing
013 * permissions and limitations under the License.
014 */
015
016package org.kuali.mobility.push.controllers;
017
018import java.sql.Timestamp;
019import java.text.DecimalFormat;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024import javax.servlet.http.Cookie;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import net.sf.json.JSONException;
029import net.sf.json.JSONObject;
030import net.sf.json.JSONSerializer;
031
032import org.apache.commons.collections.map.HashedMap;
033import org.apache.log4j.Logger;
034import org.kuali.mobility.push.entity.Device;
035import org.kuali.mobility.push.service.DeviceService;
036import org.kuali.mobility.security.authn.util.AuthenticationConstants;
037import org.kuali.mobility.security.user.api.User;
038import org.kuali.mobility.security.user.api.UserDao;
039import org.springframework.beans.factory.annotation.Autowired;
040import org.springframework.beans.factory.annotation.Qualifier;
041import org.springframework.http.HttpStatus;
042import org.springframework.http.ResponseEntity;
043import org.springframework.stereotype.Controller;
044import org.springframework.ui.Model;
045import org.springframework.web.bind.annotation.PathVariable;
046import org.springframework.web.bind.annotation.RequestMapping;
047import org.springframework.web.bind.annotation.RequestMethod;
048import org.springframework.web.bind.annotation.RequestParam;
049import org.springframework.web.bind.annotation.ResponseBody;
050
051
052/**
053 * Controller for managing devices
054 * 
055 * @author Kuali Mobility Team (mobility.dev@kuali.org)
056 * @since 2.0.0
057 */
058@Controller 
059@RequestMapping("/device")
060public class DeviceController {
061
062        /** A reference to a logger */
063        private static final Logger LOG = Logger.getLogger(DeviceController.class);
064        
065        /**
066         * A reference to the Controllers <code>DeviceService</code> object.
067         */
068        @Autowired
069        private DeviceService deviceService;
070
071        /**
072         *  A reference to the Controllers <code>UserDao</code> object. 
073         */
074        @Autowired
075        @Qualifier("kmeUserDao")
076        private UserDao userDao;
077        
078        /**
079         * A controller method for loading the main index.jsp for the Device tool.
080         * 
081         * @param uiModel
082         * @return
083         */
084        @RequestMapping(method = RequestMethod.GET)
085        public String index(Model uiModel, HttpServletRequest request) {
086        Cookie cks[] = request.getCookies();
087        String deviceIdentifier = "";
088        if (cks != null) {
089                for(Cookie c : cks){
090                    if(c.getName().equals("device.identifier")){
091                        deviceIdentifier = c.getValue();
092                    }
093                }
094        }
095        
096        Device thisDevice = null;
097        if(!deviceIdentifier.equals("")){
098                thisDevice = deviceService.findDeviceByDeviceId(deviceIdentifier);
099        }
100        
101        DecimalFormat df = new DecimalFormat("###,###,###,###.###");
102        uiModel.addAttribute("totalDeviceCount", df.format(deviceService.countDevices()));      
103        uiModel.addAttribute("thisDevice", thisDevice);
104                return "device/index";
105        }
106
107        /**
108         * A controller method for loading the keyword device search page. 
109         * 
110         * @param uiModel
111         * @param request
112         * @return
113         */
114        @RequestMapping(value="search", method = RequestMethod.GET)
115    public String search(Model uiModel, HttpServletRequest request) {
116                return "device/search";
117    }  
118        
119        /**
120         * A Controller method for accessing the details of a given device and displaying it in the association jsp file. 
121         * 
122         * @param uiModel
123         * @param request
124         * @param deviceHash Id of the device to show. 
125         * @return
126         */
127        @SuppressWarnings("unchecked")
128        @RequestMapping(value = "/detail/{deviceHash}", method = RequestMethod.GET)
129        public String getUserDetails(Model uiModel, HttpServletRequest request, @PathVariable("deviceHash") String deviceHash) {
130                if (deviceHash != null) {
131                        LOG.debug("DeviceID is :" + deviceHash);
132                }else{
133                        return "push";
134                }
135                Device device = getDeviceService().findDeviceByDeviceId(deviceHash);
136                uiModel.addAttribute("device", device);
137                uiModel.addAttribute("deviceHash", deviceHash);
138                return "device/devicedetail";
139        }
140
141        /**
142         * A controller method for accessing a deleting a specified device. 
143         * 
144         * @param uiModel
145         * @param request
146         * @param deviceHash Id of the device to delete. 
147         * @return
148         */
149        @SuppressWarnings("unchecked")
150        @RequestMapping(value = "/remove/{deviceHash}", method = RequestMethod.GET)
151        public String removeDevice(Model uiModel, HttpServletRequest request, @PathVariable("deviceHash") String deviceHash) {
152                Device deviceToDelete = getDeviceService().findDeviceByDeviceId(deviceHash);
153                if(deviceToDelete != null){
154                        LOG.debug("Will delete device with Id: " + deviceToDelete.getId());
155                        if(getDeviceService().removeDevice(deviceToDelete)){
156                                LOG.debug("Did delete device.");
157                        }
158                }
159//              return "redirect:../list";
160                return "redirect:/device";
161        }       
162        
163        /**
164         * A controller method for accessing all devices registered with push notifications. 
165         * 
166         * @param request
167         * @param uiModel
168         * @param key
169         * @return
170         */
171        @RequestMapping(value = "/list", method = RequestMethod.GET)
172        public String devices(HttpServletRequest request, Model uiModel, @RequestParam(value="key", required=false) String key) { 
173                
174                Long deviceCount = getDeviceService().countDevices();
175                int deviceNoUserCount = getDeviceService().countDevicesWithoutUsername();
176                List<String> deviceTypes = getDeviceService().getSupportedDeviceTypes();
177                Map<String, List<Device>> devicesMap = getDeviceService().findDevicesMap();
178                Map<String, Long> deviceCountMap = new HashedMap();
179                for(String deviceType : deviceTypes){
180                        Long count = new Long(getDeviceService().countDevices(deviceType));
181                        deviceCountMap.put(deviceType, count);
182                }
183
184                uiModel.addAttribute("deviceNoUserCount", deviceNoUserCount);
185                uiModel.addAttribute("deviceCount", deviceCount);
186                uiModel.addAttribute("deviceTypes", deviceTypes);
187                uiModel.addAttribute("devicesMap", devicesMap);
188                uiModel.addAttribute("deviceCountMap", deviceCountMap);
189
190                return "device/devices";
191        }
192                
193        /**
194         * A controller method for registering a device for push notifications as defined by a JSON formatted string. 
195         * 
196         * @param request
197         * @param data JSON formatted string describing a device to be register for push notifications. 
198         * @return
199         */
200        @RequestMapping(value = "/register", method = RequestMethod.GET)
201        public ResponseEntity<String> register(HttpServletRequest request, @RequestParam(value="data", required=true) String data) {
202                LOG.info("-----Register-----");
203                if(data == null){
204                        return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
205                } 
206
207                JSONObject queryParams;
208                try{
209                        queryParams = (JSONObject) JSONSerializer.toJSON(data);
210                        LOG.info(queryParams.toString());
211                }catch(JSONException je){
212                        LOG.error("JSONException in :" + data + " : " + je.getMessage());
213                        return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
214                }
215                        
216                Device device = new Device();
217                device.setDeviceName(queryParams.getString("name"));
218                device.setDeviceId(queryParams.getString("deviceId"));
219                device.setRegId(queryParams.getString("regId"));
220                device.setType(queryParams.getString("type"));
221                
222                // We might not have a username yet
223                if (queryParams.containsKey("username")){
224                        device.setUsername(queryParams.getString("username"));
225                }
226                device.setPostedTimestamp(new Timestamp(System.currentTimeMillis()));
227                LOG.info("\n-----New Device-----" + device.toString());
228
229                Device temp = getDeviceService().findDeviceByDeviceId(device.getDeviceId());
230
231                User user = (User)request.getSession().getAttribute(AuthenticationConstants.KME_USER_KEY);
232                if( user == null ) {
233                        LOG.error("No user found in request. This should never happen!");
234                } else if( user.isPublicUser() ) {
235                        LOG.debug("Public user found, no user profile updates necessary.");
236                } else if( !user.getLoginName().equals(device.getUsername()) ) {
237                        LOG.debug("User on device does not match user in session! This should never happen either.");
238                } else if( user.attributeExists(AuthenticationConstants.DEVICE_ID,device.getDeviceId())) {
239                        LOG.debug("Device id already exists on user and no action needs to be taken.");
240                } else {
241                        user.addAttribute(AuthenticationConstants.DEVICE_ID,device.getDeviceId());
242                        getUserDao().saveUser(user);
243                }
244
245                // If the device already exists, update. 
246                try {
247                        if(temp != null){
248                                LOG.info("-----Device already exists." + temp.toString());
249                                temp.setDeviceName(queryParams.getString("name"));
250                                temp.setRegId(queryParams.getString("regId"));
251                                temp.setType(queryParams.getString("type"));
252                                // We might not have a username yet
253                                if (queryParams.containsKey("username")){
254                                        temp.setUsername(queryParams.getString("username"));
255                                }
256                                temp.setPostedTimestamp(new Timestamp(System.currentTimeMillis()));
257                                getDeviceService().saveDevice(temp);
258                        }else{
259                                LOG.info("-----Device Doesn't already exist." + device.toString());
260                                getDeviceService().saveDevice(device);
261                        }
262                } catch (Exception e) {
263                        LOG.error("Exception while trying to update device", e);
264                        return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
265                }               
266                
267                return new ResponseEntity<String>(HttpStatus.OK);
268        }
269        
270        /**
271         * A controller method for updating a pre-existing registered device. 
272         * 
273         * @param request
274         * @param data A JSON formatted string describing details of a device to update. 
275         * @return
276         */
277        @RequestMapping(value = "/update", method = RequestMethod.GET)
278        public ResponseEntity<String> update(HttpServletRequest request,@RequestParam(value="data", required=true) String data) {
279                LOG.info("-----Register-----");
280                if(data == null){
281                        return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
282                } 
283
284                JSONObject queryParams;
285                try{
286                        queryParams = (JSONObject) JSONSerializer.toJSON(data);
287                        LOG.info(queryParams.toString());
288                }catch(JSONException je){
289                        LOG.error("JSONException in :" + data + " : " + je.getMessage());
290                        return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
291                }
292
293                Device temp = getDeviceService().findDeviceByDeviceId(queryParams.getString("deviceId"));
294
295                // If the device already exists, update.
296                if(temp != null){
297                        LOG.info("-----Device already exists." + temp.toString());
298                        // Remove this device from the logged user's profile if it exists.
299                        User user = getUserDao().loadUserByLoginName(temp.getUsername());
300                        if( user != null ) {
301                                user.removeAttribute(AuthenticationConstants.DEVICE_ID,temp.getDeviceId());
302                                getUserDao().saveUser(user);
303                        }
304                        temp.setDeviceName(queryParams.getString("name"));
305                        temp.setUsername(queryParams.getString("username"));
306                        temp.setPostedTimestamp(new Timestamp(System.currentTimeMillis()));
307                        getDeviceService().saveDevice(temp);
308
309                        // Find the real user and set the new device.
310                        User user2 = (User)request.getSession().getAttribute(AuthenticationConstants.KME_USER_KEY);
311                        if( user2 == null ) {
312                                LOG.error("No user found in request. This should never happen!");
313                        } else if( user2.isPublicUser() ) {
314                                LOG.debug("Public user found, no user profile updates necessary.");
315                        } else if( !user2.getLoginName().equals(temp.getUsername()) ) {
316                                LOG.debug("User on device does not match user in session! This should never happen either.");
317                        } else if( user2.attributeExists(AuthenticationConstants.DEVICE_ID,temp.getDeviceId())) {
318                                LOG.debug("Device id already exists on user and no action needs to be taken.");
319                        } else {
320                                user2.addAttribute(AuthenticationConstants.DEVICE_ID,temp.getDeviceId());
321                                getUserDao().saveUser(user2);
322                        }
323
324                }
325
326                return new ResponseEntity<String>(HttpStatus.OK);
327        }
328        
329        /**
330         * A controller method for checking the existance of a device, and accessing basic details of said device. 
331         * 
332         * @param uiModel
333         * @param request
334         * @param deviceId Id of device to check. 
335         * @return String - A JSON formatted string giving basic details of the specified device. 
336         */
337        @RequestMapping(value = "/deviceid", method = RequestMethod.POST)
338        @ResponseBody
339        public String getUsernameFromDeviceID(Model uiModel, HttpServletRequest request, @RequestParam(value="deviceid", required=false) String deviceId) {
340                Device device = getDeviceService().findDeviceByDeviceId(deviceId);
341                if(device == null){
342                        return "{\"deviceExists\":false}";                      
343                }
344                if(device.getUsername() == null || device.getUsername().length() == 0){
345                        return "{\"deviceExists\":true,\"hasUsername\":false}";
346                }else{
347                        return "{\"deviceExists\":true,\"hasUsername\":true,\"username\":\"" + device.getUsername() + "\"}";
348                }
349        }  
350
351        /**
352         * A controller method for accessing all devices registered the correspond to a specified user. 
353         * 
354         * @param uiModel
355         * @param request
356         * @param username Username associated with devices to access. 
357         * @return String - A JSON formatted string listing devices associated with a username. 
358         */
359        @RequestMapping(value = "/username", method = RequestMethod.POST)
360        @ResponseBody
361        public String getDevicesFromUsername(Model uiModel, HttpServletRequest request, @RequestParam(value="username", required=false) String username) {
362                // TODO use JSONObject to build json here??
363                List<Device> devices = getDeviceService().findDevicesByUsername(username);
364                String result = "{\"username\":\"" + username + "\",\"devices\":[";
365                String sDevices = "";
366                Iterator<Device> i = devices.iterator();
367                
368                while(i.hasNext()){
369                        Device d = (Device)i.next();
370                        sDevices += "\"" + d.getDeviceId() + "\"," ;
371                }
372                
373                if(sDevices.length() > 0){
374                        sDevices = sDevices.substring(0, sDevices.length() - 1);
375                }
376                
377                result += sDevices + "]}";
378                return result;
379        }
380        
381        /**
382         * A controller method that provides an interface for search devices via keyword and returns a JSON list to the requesting JSP/HMLT page. 
383         * 
384         * @param request
385         * @param response
386         * @param uiModel
387         * @param keyword
388         * @return
389         */
390    @RequestMapping(value = "keyword/{keyword}", method = RequestMethod.GET, headers = "Accept=application/json")
391    @ResponseBody
392    public String devicesByKeyword(HttpServletRequest request, HttpServletResponse response, Model uiModel, @PathVariable("keyword") String keyword) {  
393                List<Device> devices = deviceService.findDevicesByKeyword(keyword);
394                LOG.info(devices.size() + " elements returned.");
395
396                String json = "";
397                if(devices.size() > 0){
398                        Iterator<Device> i = devices.iterator();  
399                        if(devices.size() > 1){
400                                json = "{\"total\":\"" + devices.size() + " Devices Found.\" , \"devices\":[";
401                        }else{
402                                json = "{\"total\":\"" + devices.size() + " Device Found.\" , \"devices\":[";
403                        }                       
404                        while(i.hasNext()){
405                                json += ((Device)i.next()).toJson();
406                                json += ",";
407                        }
408                        json = json.substring(0, json.length()-1);
409                        json += "]}";           
410                }else{
411                        json = "{\"total\":\"No Devices Found\"}";
412                }
413                
414                return json;
415    }
416        
417        
418        /**
419         * Simple controller which responds with 200.
420         * This controller can be used by devices to check if the server is online
421         * @return
422         */
423        @RequestMapping(value = "/ping", method = RequestMethod.GET)
424        public ResponseEntity<String> ping() {
425                return new ResponseEntity<String>(HttpStatus.OK);
426        }       
427
428        /**
429         * Sets the reference to the <code>DeviceService</code>
430         * @param deviceService
431         */
432        public void setDeviceService(DeviceService deviceService){
433                this.deviceService = deviceService;
434        }
435
436        /**
437         * A reference to the <code>DeviceService</code>.
438         */
439        public DeviceService getDeviceService() {
440                return deviceService;
441        }
442
443        /**
444         * Get the controllers <code>UserDao</code> object.
445         * @return
446         */
447        public UserDao getUserDao() {
448                return userDao;
449        }
450
451        /**
452         * A method for setting the <code>UserDao</code> of the controller. 
453         * 
454         * @param userDao
455         */
456        public void setUserDao(UserDao userDao) {
457                this.userDao = userDao;
458        }
459}