package org.fluxtream.mvc.controllers; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.fluxtream.core.Configuration; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.domain.ApiUpdate; import org.fluxtream.core.domain.ConnectorInfo; import org.fluxtream.core.domain.Guest; import org.fluxtream.core.domain.UpdateWorkerTask; import org.fluxtream.core.mvc.models.admin.ConnectorInstanceModelFactory; import org.fluxtream.core.services.ConnectorUpdateService; import org.fluxtream.core.services.GuestService; import org.fluxtream.core.services.JPADaoService; import org.fluxtream.core.services.SystemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * User: candide * Date: 17/09/13 * Time: 12:24 */ @Controller public class AdminController { @Autowired GuestService guestService; @Autowired ConnectorInstanceModelFactory connectorInstanceModelFactory; @Autowired ConnectorUpdateService connectorUpdateService; @Autowired Configuration env; @Autowired SystemService systemService; @Autowired JPADaoService jpaDaoService; @Secured({ "ROLE_ADMIN" }) @RequestMapping(value = { "/admin/fail" }) public ModelAndView getPermanentFailConnectors(HttpServletResponse response, @RequestParam(value = "filter", required=false, defaultValue = "apiKey.reason!='" + ApiKey.PermanentFailReason.NEEDS_REAUTH + "'") String filter, @RequestParam(value = "guestId", required=false) String guestId, @RequestParam(value = "status_0", required=false, defaultValue="false") boolean statusUp, @RequestParam(value = "status_1", required=false, defaultValue="false") boolean statusPermanentFail, @RequestParam(value = "status_2", required=false, defaultValue="false") boolean statusTemporaryFail, @RequestParam(value = "status_3", required=false, defaultValue="false") boolean statusOverRateLimit) throws Exception { response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1. response.setHeader("Pragma", "no-cache"); // HTTP 1.0. response.setDateHeader("Expires", 0); final List<ApiKey.Status> statusFilters = new ArrayList<ApiKey.Status>(); if (statusUp) statusFilters.add(ApiKey.Status.STATUS_UP); if (statusPermanentFail) statusFilters.add(ApiKey.Status.STATUS_PERMANENT_FAILURE); if (statusTemporaryFail) statusFilters.add(ApiKey.Status.STATUS_TRANSIENT_FAILURE); if (statusOverRateLimit) statusFilters.add(ApiKey.Status.STATUS_OVER_RATE_LIMIT); ModelAndView mav = new ModelAndView("admin/permanentFail"); final StringBuffer queryString = new StringBuffer("SELECT apiKey FROM ApiKey apiKey "); StringBuffer whereClause = new StringBuffer(); boolean whereClauseAdded = addPotentialStatusWhereClause(whereClause, statusFilters); if (!StringUtils.isEmpty(guestId)) { addGuestIdClause(whereClause, guestId, whereClauseAdded); whereClauseAdded = true; } if (!StringUtils.isEmpty(filter)) { addFilterClause(whereClause, filter, whereClauseAdded); whereClauseAdded = true; } if (whereClauseAdded) queryString.append(whereClause).append(" "); queryString.append("ORDER BY apiKey.api, apiKey.guestId"); System.out.println(queryString.toString()); final List<ApiKey> apiKeys = jpaDaoService.executeQueryWithLimitAndOffset(queryString.toString(), Integer.MAX_VALUE, 0, ApiKey.class); mav.addObject("statusFilters", statusFilters); mav.addObject("apiKeys", apiKeys); mav.addObject("filter", filter); mav.addObject("guestId", guestId); mav.addObject("release", env.get("release")); return mav; } private void addFilterClause(final StringBuffer whereClause, final String filter, final boolean whereClauseAdded) { if (whereClauseAdded) whereClause.append(" AND " + filter); else whereClause.append(" WHERE " + filter); } private void addGuestIdClause(final StringBuffer whereClause, final String guestId, final boolean whereClauseAdded) { if (whereClauseAdded) whereClause.append(" AND guestId=" + guestId); else whereClause.append(" WHERE guestId=" + guestId); } private boolean addPotentialStatusWhereClause(final StringBuffer whereClause, final List<ApiKey.Status> statusFilters) { boolean whereClauseAdded = false; StringBuffer statusWhereClause = new StringBuffer(); for (ApiKey.Status statusFilter : statusFilters) { addStatusWhereClauseTerm(statusWhereClause, whereClauseAdded, statusFilter.ordinal()); whereClauseAdded = true; } if (whereClauseAdded) whereClause.append("WHERE ").append("(").append(statusWhereClause).append(")"); return whereClauseAdded; } private boolean addStatusWhereClauseTerm(final StringBuffer whereClause, boolean whereClauseAdded, final int statusValue) { if (whereClauseAdded) whereClause.append(" OR "); whereClause.append("apiKey.status=" + statusValue); whereClauseAdded = true; return whereClauseAdded; } @Secured({ "ROLE_ADMIN" }) @RequestMapping(value = { "/admin" }) public ModelAndView admin(HttpServletResponse response, @RequestParam(value = "page", required = false, defaultValue = "1") int page, @RequestParam(value = "pageSize", required = false, defaultValue = "20") int pageSize) throws Exception { response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1. response.setHeader("Pragma", "no-cache"); // HTTP 1.0. response.setDateHeader("Expires", 0); ModelAndView mav = new ModelAndView("admin/index"); long totalGuests = jpaDaoService.executeNativeQuery("SELECT count(*) from Guest"); // If pageSize is too small, set to default of 20 if(pageSize<=0) pageSize = 20; // Limit range of page to be >=1 and <= lastPage) int lastPage = ((int)totalGuests)%pageSize==0 ? ((int)totalGuests)/pageSize : ((int)totalGuests)/pageSize+1; if(page<1) page=1; else if(page>lastPage) page=lastPage; final int offset = (page - 1) * pageSize; final List<Guest> allGuests = jpaDaoService.executeQueryWithLimitAndOffset("SELECT guest FROM Guest guest", pageSize, offset, Guest.class); // get scheduled updateWorkerTasks for the current subset of users final List<UpdateWorkerTask> tasks = connectorUpdateService.getAllScheduledUpdateWorkerTasks(); mav.addObject("allGuests", allGuests); mav.addObject("release", env.get("release")); final List<ConnectorInfo> connectors = systemService.getConnectors(); mav.addObject("subview", "connectorHealthDashboard"); mav.addObject("connectors", connectors); List<Map.Entry<Guest,List<List<ApiKey>>>> rows = new ArrayList<Map.Entry<Guest,List<List<ApiKey>>>>(); ValueHolder synching = getSynchingUpdateWorkerTasks(); long consumerTriggerRepeatInterval = Long.valueOf(env.get("consumer.trigger.repeatInterval")); ValueHolder due = getDueUpdateWorkerWorkerTasks(tasks, consumerTriggerRepeatInterval); ValueHolder overdue = getOverdueUpdateWorkerWorkerTasks(tasks, consumerTriggerRepeatInterval); for (Guest guest : allGuests) { List<List<ApiKey>> guestApiKeys = new ArrayList<List<ApiKey>>(); for (ConnectorInfo connector : connectors) { final List<ApiKey> apiKeys = guestService.getApiKeys(guest.getId(), Connector.fromValue(connector.api)); guestApiKeys.add(apiKeys); } final Map.Entry<Guest, List<List<ApiKey>>> guestListEntry = new AbstractMap.SimpleEntry<Guest, List<List<ApiKey>>>(guest, guestApiKeys); rows.add(guestListEntry); } mav.addObject("totalGuests", totalGuests); mav.addObject("fromGuest", offset); mav.addObject("toGuest", offset + allGuests.size()); mav.addObject("page", page); mav.addObject("pageSize", pageSize); mav.addObject("synching", synching); mav.addObject("tasksDue", due); mav.addObject("tasksOverdue", overdue); mav.addObject("rows", rows); mav.addObject("serverUUID", connectorUpdateService.getLiveServerUUIDs().toString()); return mav; } private ValueHolder getDueUpdateWorkerWorkerTasks(final List<UpdateWorkerTask> tasks, long consumerTriggerRepeatInterval) { ValueHolder due = new ValueHolder(); for (UpdateWorkerTask task : tasks) { if (task.timeScheduled>System.currentTimeMillis()-consumerTriggerRepeatInterval) { if (due.get(task.apiKeyId)!=null) due.put(task.apiKeyId, due.get(task.apiKeyId)+1); else due.put(task.apiKeyId, 1); } } return due; } private ValueHolder getOverdueUpdateWorkerWorkerTasks(final List<UpdateWorkerTask> tasks, long consumerTriggerRepeatInterval) { ValueHolder overdue = new ValueHolder(); for (UpdateWorkerTask task : tasks) { if (task.timeScheduled<=System.currentTimeMillis()-consumerTriggerRepeatInterval) { if (overdue.get(task.apiKeyId)!=null) overdue.put(task.apiKeyId, overdue.get(task.apiKeyId)+1); else overdue.put(task.apiKeyId, 1); } } return overdue; } private ValueHolder getSynchingUpdateWorkerTasks() { ValueHolder synching = new ValueHolder(); final List<UpdateWorkerTask> tasks = connectorUpdateService.getAllSynchingUpdateWorkerTasks(); for (UpdateWorkerTask task : tasks) { if (synching.get(task.apiKeyId)!=null) synching.put(task.apiKeyId, synching.get(task.apiKeyId)+1); else synching.put(task.apiKeyId, 1); } return synching; } public class ValueHolder { Map<Long,Integer> dict = new HashMap<Long,Integer>(); public void put(Long l, Integer i) { dict.put(l, i); } public Integer get(Long l) { return dict.get(l); } } @Secured({ "ROLE_ADMIN" }) @RequestMapping("/admin/{guestId}/{apiKeyId}/{objectTypes}/historyUpdate") public ModelAndView forceConnectorInstanceHistoryUpdate(@PathVariable("guestId") long guestId, @PathVariable("apiKeyId") long apiKeyId, @PathVariable("objectTypes") int objectTypes) { final ApiKey apiKey = guestService.getApiKey(apiKeyId); connectorUpdateService.updateConnectorObjectType(apiKey, objectTypes, true, true); return new ModelAndView(String.format("redirect:/admin/%s/%s", guestId, apiKeyId)); } @Secured({ "ROLE_ADMIN" }) @RequestMapping("/admin/{guestId}/{apiKeyId}/{objectTypes}/refresh") public ModelAndView refreshConnectorInstance(@PathVariable("guestId") long guestId, @PathVariable("apiKeyId") long apiKeyId, @PathVariable("objectTypes") int objectTypes) { final ApiKey apiKey = guestService.getApiKey(apiKeyId); connectorUpdateService.updateConnectorObjectType(apiKey, objectTypes, true, false); return new ModelAndView(String.format("redirect:/admin/%s/%s", guestId, apiKeyId)); } public ModelAndView getAdminModel() { ModelAndView mav = new ModelAndView("admin/index"); final List<Guest> allGuests = guestService.getAllGuests(); mav.addObject("allGuests", allGuests); mav.addObject("release", env.get("release")); return mav; } @Secured({ "ROLE_ADMIN" }) @RequestMapping(value = "/admin/{guestId}/{apiKeyId}/setToPermanentFail") public ModelAndView setToPermanentFail(@PathVariable("guestId") long guestId, @PathVariable("apiKeyId") long apiKeyId) throws Exception { guestService.setApiKeyStatus(apiKeyId, ApiKey.Status.STATUS_PERMANENT_FAILURE, null, ApiKey.PermanentFailReason.MANUAL_INTERVENTION); return new ModelAndView(String.format("redirect:/admin/%s/%s", guestId, apiKeyId)); } @Secured({ "ROLE_ADMIN" }) @RequestMapping("/admin/{guestId}/{apiKeyId}") public ModelAndView showConnectorInstanceDetails(@PathVariable("guestId") long guestId, @PathVariable("apiKeyId") long apiKeyId) { ModelAndView mav = getAdminModel(); mav.addObject("subview", "connectorDetails"); final ApiKey apiKey = guestService.getApiKey(apiKeyId); final Map<String, Object> connectorInstanceModel = connectorInstanceModelFactory.createConnectorInstanceModel(apiKey); final Guest guest = guestService.getGuestById(guestId); final List<ApiUpdate> lastUpdates = connectorUpdateService.getUpdates(apiKey, 100, 0); mav.addObject("guest", guest); mav.addObject("guestId", guest.getId()); mav.addObject("apiKeyId", apiKeyId); mav.addObject("apiKey", apiKey); mav.addObject("attributes", guestService.getApiKeyAttributes(apiKeyId)); mav.addObject("connectorInstanceModel", connectorInstanceModel); mav.addObject("lastUpdates", lastUpdates); mav.addObject("liveServerUUIDs", connectorUpdateService.getLiveServerUUIDs()); List<UpdateWorkerTask> scheduledTasks = getScheduledTasks(apiKey); mav.addObject("scheduledTasks", scheduledTasks); return mav; } private List<UpdateWorkerTask> getScheduledTasks(final ApiKey apiKey) { final int[] objectTypeValues = apiKey.getConnector().objectTypeValues(); List<UpdateWorkerTask> scheduledTasks = new ArrayList<UpdateWorkerTask>(); if (apiKey.getConnector().isAutonomous()) { final List<UpdateWorkerTask> updateWorkerTasks = connectorUpdateService.getUpdateWorkerTasks(apiKey, 0, 10); scheduledTasks.addAll(updateWorkerTasks); } else { for (int objectTypeValue : objectTypeValues) { final List<UpdateWorkerTask> updateWorkerTasks = connectorUpdateService.getUpdateWorkerTasks(apiKey, objectTypeValue, 10); scheduledTasks.addAll(updateWorkerTasks); } } Collections.sort(scheduledTasks, new Comparator<UpdateWorkerTask>() { @Override public int compare(final UpdateWorkerTask o1, final UpdateWorkerTask o2) { return (int)(o2.timeScheduled - o1.timeScheduled); } }); return scheduledTasks; } @Secured({ "ROLE_ADMIN" }) @RequestMapping("/admin/{guestId}") public ModelAndView showUserApiKeys(@PathVariable("guestId") long guestId) { ModelAndView mav = getAdminModel(); mav.addObject("subview", "allConnectors"); final Guest guest = guestService.getGuestById(guestId); final List<ApiKey> apiKeys = guestService.getApiKeys(guest.getId()); mav.addObject("username", guest.username); mav.addObject("guestId", guest.getId()); mav.addObject("connectorInstanceModels", getConnectorInstanceModels(apiKeys)); return mav; } private Object getConnectorInstanceModels(final List<ApiKey> apiKeys) { Map<Long, Map<String,Object>> connectorInstanceModels = new HashMap<Long, Map<String,Object>>(); for (ApiKey key : apiKeys) { final Map<String, Object> connectorInstanceModel = connectorInstanceModelFactory.createConnectorInstanceModel(key); connectorInstanceModels.put(key.getId(), connectorInstanceModel); } return connectorInstanceModels; } }