/* ================================================================== * UserAlertController.java - 19/05/2015 7:35:10 pm * * Copyright 2007-2015 SolarNetwork.net Dev Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== */ package net.solarnetwork.central.reg.web; import static net.solarnetwork.web.domain.Response.response; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import net.solarnetwork.central.query.biz.QueryBiz; import net.solarnetwork.central.security.SecurityUser; import net.solarnetwork.central.security.SecurityUtils; import net.solarnetwork.central.user.biz.UserAlertBiz; import net.solarnetwork.central.user.biz.UserBiz; import net.solarnetwork.central.user.domain.UserAlert; import net.solarnetwork.central.user.domain.UserAlertOptions; import net.solarnetwork.central.user.domain.UserAlertSituationStatus; import net.solarnetwork.central.user.domain.UserAlertStatus; import net.solarnetwork.central.user.domain.UserAlertType; import net.solarnetwork.util.StringUtils; import net.solarnetwork.web.domain.Response; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; /** * Controller for user alerts. * * @author matt * @version 1.1 */ @Controller @RequestMapping("/sec/alerts") public class UserAlertController extends ControllerSupport { private static final Pattern TIME_PAT = Pattern.compile("[0-2]?\\d:[0-5]\\d"); private final UserBiz userBiz; private final UserAlertBiz userAlertBiz; private final QueryBiz queryBiz; @Autowired public UserAlertController(UserBiz userBiz, UserAlertBiz userAlertBiz, QueryBiz queryBiz) { super(); this.userBiz = userBiz; this.userAlertBiz = userAlertBiz; this.queryBiz = queryBiz; } @ModelAttribute("nodeDataAlertTypes") public List<UserAlertType> nodeDataAlertTypes() { // now now, only one alert type! return Collections.singletonList(UserAlertType.NodeStaleData); } @ModelAttribute("alertStatuses") public UserAlertStatus[] alertStatuses() { return UserAlertStatus.values(); } /** * View the main Alerts screen. * * @param model * The model object. * @return The view name. */ @RequestMapping(value = "", method = RequestMethod.GET) public String view(Model model) { final SecurityUser user = SecurityUtils.getCurrentUser(); List<UserAlert> alerts = userAlertBiz.userAlertsForUser(user.getUserId()); if ( alerts != null ) { List<UserAlert> nodeDataAlerts = new ArrayList<UserAlert>(alerts.size()); for ( UserAlert alert : alerts ) { switch (alert.getType()) { case NodeStaleData: nodeDataAlerts.add(alert); break; } } model.addAttribute("nodeDataAlerts", nodeDataAlerts); } model.addAttribute("userNodes", userBiz.getUserNodes(user.getUserId())); return "alerts/view-alerts"; } /** * Get all available sources for a given node ID. * * @param nodeId * The ID of the node to get all available sources for. * @param start * An optional start date to limit the query to. * @param end * An optional end date to limit the query to. * @return The found sources. */ @RequestMapping(value = "/node/{nodeId}/sources", method = RequestMethod.GET) @ResponseBody public Response<List<String>> availableSourcesForNode(@PathVariable("nodeId") Long nodeId, @RequestParam(value = "start", required = false) DateTime start, @RequestParam(value = "end", required = false) DateTime end) { Set<String> sources = queryBiz.getAvailableSources(nodeId, start, end); List<String> sourceList = new ArrayList<String>(sources); return response(sourceList); } /** * Get <em>active</em> situations for a given node. * * @param nodeId * The ID of the node to get the sitautions for. * @param locale * The request locale. * @return The alerts with active situations. * @since 1.1 */ @RequestMapping(value = "/node/{nodeId}/situations", method = RequestMethod.GET) @ResponseBody public Response<List<UserAlert>> activeSituationsNode(@PathVariable("nodeId") Long nodeId, Locale locale) { List<UserAlert> results = userAlertBiz.alertSituationsForNode(nodeId); for ( UserAlert alert : results ) { populateUsefulAlertOptions(alert, locale); } return response(results); } /** * Get a count of <em>active</em> situations for the active user. * * @return The count. * @since 1.1 */ @RequestMapping(value = "/user/situation/count", method = RequestMethod.GET) @ResponseBody public Response<Integer> activeSituationCount() { Long userId = SecurityUtils.getCurrentActorUserId(); Integer count = userAlertBiz.alertSituationCountForUser(userId); return response(count); } /** * Get <em>active</em> situations for the active user * * @param locale * The request locale. * @return The alerts with active situations. * @since 1.1 */ @RequestMapping(value = "/user/situations", method = RequestMethod.GET) @ResponseBody public Response<List<UserAlert>> activeSituations(Locale locale) { Long userId = SecurityUtils.getCurrentActorUserId(); List<UserAlert> results = userAlertBiz.alertSituationsForUser(userId); for ( UserAlert alert : results ) { populateUsefulAlertOptions(alert, locale); } return response(results); } /** * Create or update an alert. * * @param model * The UserAlert details. * @return The saved details. */ @RequestMapping(value = "/save", method = RequestMethod.POST) @ResponseBody public Response<UserAlert> addAlert(@RequestBody UserAlert model) { final SecurityUser user = SecurityUtils.getCurrentUser(); UserAlert alert = new UserAlert(); alert.setId(model.getId()); alert.setNodeId(model.getNodeId()); alert.setUserId(user.getUserId()); alert.setCreated(new DateTime()); alert.setStatus(model.getStatus() == null ? UserAlertStatus.Active : model.getStatus()); alert.setType(model.getType() == null ? UserAlertType.NodeStaleData : model.getType()); // reset validTo date to now, so alert re-processed alert.setValidTo(new DateTime()); Map<String, Object> options = new HashMap<String, Object>(); if ( model.getOptions() != null ) { for ( Map.Entry<String, Object> me : model.getOptions().entrySet() ) { if ( "ageMinutes".equalsIgnoreCase(me.getKey()) ) { // convert ageMinutes to age (seconds) Object v = model.getOptions().get("ageMinutes"); double minutes = 1; try { minutes = Double.parseDouble(v.toString()); } catch ( NumberFormatException e ) { // ignore log.warn("Alert option ageMinutes is not a number, setting to 1: [{}]", v); } options.put(UserAlertOptions.AGE_THRESHOLD, Math.round(minutes * 60.0)); } else if ( "sources".equalsIgnoreCase(me.getKey()) && me.getValue() != null ) { // convert sources to List of String Set<String> sources = StringUtils .commaDelimitedStringToSet(me.getValue().toString()); if ( sources != null ) { List<String> sourceList = new ArrayList<String>(sources); options.put(UserAlertOptions.SOURCE_IDS, sourceList); } } else if ( "windows".equalsIgnoreCase(me.getKey()) && me.getValue() instanceof Collection ) { @SuppressWarnings("unchecked") Collection<Map<String, ?>> windows = (Collection<Map<String, ?>>) me.getValue(); List<Map<String, Object>> windowsList = new ArrayList<Map<String, Object>>(); for ( Map<String, ?> window : windows ) { Object timeStart = window.get("timeStart"); Object timeEnd = window.get("timeEnd"); if ( timeStart != null && timeEnd != null ) { String ts = timeStart.toString(); String te = timeEnd.toString(); if ( TIME_PAT.matcher(ts).matches() && TIME_PAT.matcher(te).matches() ) { Map<String, Object> win = new LinkedHashMap<String, Object>(2); win.put("timeStart", ts); win.put("timeEnd", te); windowsList.add(win); } } } if ( windowsList.size() > 0 ) { options.put(UserAlertOptions.TIME_WINDOWS, windowsList); } } } } if ( options.size() > 0 ) { alert.setOptions(options); } Long id = userAlertBiz.saveAlert(alert); alert.setId(id); return response(alert); } private void populateUsefulAlertOptions(UserAlert alert, Locale locale) { if ( alert == null ) { return; } // to aid UI, populate some useful display properties if ( alert.getSituation() != null ) { DateTimeFormatter fmt = DateTimeFormat.forStyle("LS"); if ( locale != null ) { fmt = fmt.withLocale(locale); } alert.getOptions().put("situationDate", fmt.print(alert.getSituation().getCreated())); if ( alert.getSituation().getNotified() != null ) { alert.getOptions().put("situationNotificationDate", fmt.print(alert.getSituation().getNotified())); } } if ( alert.getOptions() != null ) { Map<String, Object> options = alert.getOptions(); if ( options.containsKey(UserAlertOptions.SOURCE_IDS) ) { @SuppressWarnings("unchecked") Collection<String> sources = (Collection<String>) options .get(UserAlertOptions.SOURCE_IDS); options.put("sources", StringUtils.commaDelimitedStringFromCollection(sources)); } } } /** * View an alert with the most recent active situation populated. * * @param alertId * The ID of the alert to view. * @param locale * The request locale. * @return The alert. */ @RequestMapping(value = "/situation/{alertId}", method = RequestMethod.GET) @ResponseBody public Response<UserAlert> viewSituation(@PathVariable("alertId") Long alertId, Locale locale) { UserAlert alert = userAlertBiz.alertSituation(alertId); populateUsefulAlertOptions(alert, locale); return response(alert); } /** * Update an active alert sitaution's status. * * @param alertId * The ID of the alert with the active situation. * @param status * The situation status to set. * @param locale * The request locale. * @return The updated alert. */ @RequestMapping(value = "/situation/{alertId}/resolve", method = RequestMethod.POST) @ResponseBody public Response<UserAlert> resolveSituation(@PathVariable("alertId") Long alertId, @RequestParam("status") UserAlertSituationStatus status, Locale locale) { UserAlert alert = userAlertBiz.updateSituationStatus(alertId, status); populateUsefulAlertOptions(alert, locale); return response(alert); } /** * Delete an alert. * * @param alertId * The ID of the alert to delete. * @return The result. */ @RequestMapping(value = "/{alertId}", method = RequestMethod.DELETE) @ResponseBody public Response<Object> deleteAlert(@PathVariable("alertId") Long alertId) { userAlertBiz.deleteAlert(alertId); return response(null); } }