package nl.topicus.onderwijs.dashboard.modules.topicus; import static nl.topicus.onderwijs.dashboard.modules.topicus.RetrieverUtils.getStatuspage; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import net.htmlparser.jericho.Element; import net.htmlparser.jericho.HTMLElementName; import net.htmlparser.jericho.Source; import nl.topicus.onderwijs.dashboard.config.ISettings; import nl.topicus.onderwijs.dashboard.datasources.ApplicationVersion; import nl.topicus.onderwijs.dashboard.datasources.AverageRequestTime; import nl.topicus.onderwijs.dashboard.datasources.NumberOfServers; import nl.topicus.onderwijs.dashboard.datasources.NumberOfServersOffline; import nl.topicus.onderwijs.dashboard.datasources.NumberOfUsers; import nl.topicus.onderwijs.dashboard.datasources.NumberOfUsersPerServer; import nl.topicus.onderwijs.dashboard.datasources.RequestsPerMinute; import nl.topicus.onderwijs.dashboard.datasources.ServerAlerts; import nl.topicus.onderwijs.dashboard.datasources.ServerStatus; import nl.topicus.onderwijs.dashboard.datasources.Uptime; import nl.topicus.onderwijs.dashboard.datatypes.Alert; import nl.topicus.onderwijs.dashboard.datatypes.DotColor; import nl.topicus.onderwijs.dashboard.keys.Key; import nl.topicus.onderwijs.dashboard.modules.AbstractService; import nl.topicus.onderwijs.dashboard.modules.DashboardRepository; import nl.topicus.onderwijs.dashboard.modules.ServiceConfiguration; import org.apache.wicket.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @ServiceConfiguration(interval = 30, unit = TimeUnit.SECONDS) public class SomOnderwijsportaalRetriever extends AbstractService implements TopicusApplicationStatusProvider { private static final Logger log = LoggerFactory .getLogger(SomOnderwijsportaalRetriever.class); private Map<Key, TopicusApplicationStatus> statusses = new HashMap<Key, TopicusApplicationStatus>(); private Map<String, Alert> oldAlerts = new HashMap<String, Alert>(); @Autowired public SomOnderwijsportaalRetriever(ISettings settings) { super(settings); } @Override public void onConfigure(DashboardRepository repository) { Map<Key, Map<String, ?>> serviceSettings = getSettings() .getServiceSettings(SomOnderwijsportaalRetriever.class); for (Key project : serviceSettings.keySet()) { repository.addDataSource(project, NumberOfUsers.class, new NumberOfUsersImpl(project, this)); repository.addDataSource(project, NumberOfServers.class, new NumberOfServersImpl(project, this)); repository.addDataSource(project, NumberOfServersOffline.class, new NumberOfServersOfflineImpl(project, this)); repository.addDataSource(project, Uptime.class, new UptimeImpl( project, this)); repository.addDataSource(project, ApplicationVersion.class, new ApplicationVersionImpl(project, this)); repository.addDataSource(project, ServerStatus.class, new ServerStatusImpl(project, this)); repository.addDataSource(project, ServerAlerts.class, new AlertsImpl(project, this)); repository.addDataSource(project, NumberOfUsersPerServer.class, new NumberOfUsersPerServerImpl(project, this)); repository.addDataSource(project, AverageRequestTime.class, new AverageRequestTimeImpl(project, this)); repository.addDataSource(project, RequestsPerMinute.class, new RequestsPerMinuteImpl(project, this)); } } @Override public TopicusApplicationStatus getStatus(Key project) { return statusses.get(project); } @SuppressWarnings("unchecked") @Override public void refreshData() { HashMap<Key, TopicusApplicationStatus> newStatusses = new HashMap<Key, TopicusApplicationStatus>(); Map<Key, Map<String, ?>> serviceSettings = getSettings() .getServiceSettings(SomOnderwijsportaalRetriever.class); for (Map.Entry<Key, Map<String, ?>> configEntry : serviceSettings .entrySet()) { Key project = configEntry.getKey(); Map<String, String> urls = (Map<String, String>) configEntry .getValue().get("urls"); TopicusApplicationStatus status = getProjectData(project, urls); newStatusses.put(project, status); } statusses = newStatusses; } private TopicusApplicationStatus getProjectData(Key project, Map<String, String> urls) { TopicusApplicationStatus status = new TopicusApplicationStatus(); if (urls == null || urls.isEmpty()) { return status; } List<Alert> alerts = new ArrayList<Alert>(); for (Entry<String, String> statusUrlEntry : urls.entrySet()) { String statusCode = statusUrlEntry.getKey(); String statusUrl = statusUrlEntry.getValue(); TopicusServerStatus server = new TopicusServerStatus(statusCode, statusUrl); status.addServer(server); Alert oldAlert = oldAlerts.get(statusUrl); try { StatusPageResponse statuspage = getStatuspage(statusUrl); if (!statuspage.isOk()) { server.setServerStatus(DotColor.RED); Alert alert = new Alert(oldAlert, DotColor.RED, project, "Server " + statusCode + " offline with HTTP code " + statuspage.getHttpStatusCode()); oldAlerts.put(statusUrl, alert); alerts.add(alert); continue; } server.setServerStatus(DotColor.GREEN); String page = statuspage.getPageContent(); Source source = new Source(page); source.fullSequentialParse(); List<Element> tableHeaders = source .getAllElements(HTMLElementName.H2); for (Element tableHeader : tableHeaders) { String contents = tableHeader.getTextExtractor().toString(); if ("Applicatie status".equals(contents)) { fetchApplicationInfo(server, tableHeader .getParentElement(), oldAlert, alerts, project); } else if ("Sessies/Requests".equals(contents)) { fetchSessionAndRequestData(server, tableHeader .getParentElement()); } } oldAlerts.put(statusUrl, null); } catch (Exception e) { server.setServerStatus(DotColor.YELLOW); Alert alert = new Alert(oldAlert, DotColor.YELLOW, project, e .getMessage()); oldAlerts.put(statusUrl, alert); alerts.add(alert); log.warn("Could not retrieve status for '" + statusUrl + "': " + e.getClass().getSimpleName() + " - " + e.getLocalizedMessage()); } } status.setAlerts(alerts); log.info("Application status: {}->{}", project, status); return status; } private void fetchApplicationInfo(TopicusServerStatus server, Element tableHeader, Alert oldAlert, List<Alert> alerts, Key project) { List<Element> tableRows = tableHeader .getAllElements(HTMLElementName.TR); for (Element curRow : tableRows) { String name = curRow.getFirstElement("th").getTextExtractor() .toString(); String value = curRow.getFirstElement("td").getTextExtractor() .toString(); if ("Versie".equals(name)) { server.setVersion(value); } else if ("Start tijd".equals(name)) { SimpleDateFormat sdf = new SimpleDateFormat( "dd MMMM yyyy, hh:mm", new Locale("NL")); try { Date starttime = sdf.parse(value); Date now = new Date(); server.setUptime(Duration.milliseconds( now.getTime() - starttime.getTime()) .getMilliseconds()); } catch (ParseException e) { log.error("Unable to parse starttime " + value + " according to format dd MMMM yyyy, hh:mm", e); } } else if ("Status".equals(name)) { if (!"OK".equals(value)) { server.setServerStatus(DotColor.RED); Alert alert = new Alert(oldAlert, DotColor.RED, project, "Server " + server.getCode() + " reports " + value); oldAlerts.put(server.getUrl(), alert); alerts.add(alert); } } } } private void fetchSessionAndRequestData(TopicusServerStatus server, Element tableHeader) { List<Element> tableRows = tableHeader .getAllElements(HTMLElementName.TR); for (Element curRow : tableRows) { String name = curRow.getFirstElement("th").getTextExtractor() .toString(); String value = curRow.getFirstElement("td").getTextExtractor() .toString(); if ("Actieve sessies".equals(name)) { try { server.setNumberOfUsers(Integer.parseInt(value)); } catch (NumberFormatException e) { log.error("Cannot parse number of users: " + value); } } else if ("Gem. request duur".equals(name)) { try { int space = value.indexOf(' '); if (space > -1) { value = value.substring(0, space); } server.setAverageRequestDuration(Integer.parseInt(value)); } catch (NumberFormatException e) { log.error("Cannot parse avg request duration: " + value); } } else if ("Requests per minuut".equals(name)) { try { server.setRequestsPerMinute(Integer.parseInt(value)); } catch (NumberFormatException e) { log.error("Cannot parse req per minute: " + value); } } } } }