All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mats3.util.eagercache.MatsEagerCacheHtmlGui Maven / Gradle / Ivy

Go to download

Mats^3 Utilities - notably the MatsFuturizer, which provides a bridge from synchronous processes to the highly asynchronous Mats^3 services.

The newest version!
package io.mats3.util.eagercache;

import static io.mats3.util.eagercache.MatsEagerCacheHtmlGui.MatsEagerCacheClientHtmlGui.includeFile;
import static io.mats3.util.eagercache.MatsEagerCacheServer.MatsEagerCacheServerImpl._formatHtmlBytes;
import static io.mats3.util.eagercache.MatsEagerCacheServer.MatsEagerCacheServerImpl._formatHtmlTimestamp;
import static io.mats3.util.eagercache.MatsEagerCacheServer.MatsEagerCacheServerImpl._formatMillis;
import static io.mats3.util.eagercache.MatsEagerCacheServer.MatsEagerCacheServerImpl._formatTimestamp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;

import io.mats3.util.FieldBasedJacksonMapper;
import io.mats3.util.eagercache.MatsEagerCacheClient.CacheClientInformation;
import io.mats3.util.eagercache.MatsEagerCacheClient.CacheUpdated;
import io.mats3.util.eagercache.MatsEagerCacheClient.MatsEagerCacheClientMock;
import io.mats3.util.eagercache.MatsEagerCacheHtmlGui.MatsEagerCacheClientHtmlGui.ReplyDto;
import io.mats3.util.eagercache.MatsEagerCacheHtmlGui.MatsEagerCacheClientHtmlGui.RequestDto;
import io.mats3.util.eagercache.MatsEagerCacheServer.CacheServerInformation;
import io.mats3.util.eagercache.MatsEagerCacheServer.ExceptionEntry;
import io.mats3.util.eagercache.MatsEagerCacheServer.LogEntry;
import io.mats3.util.eagercache.MatsEagerCacheServer.MatsEagerCacheServerImpl.CacheMonitor;

/**
 * Embeddable HTML GUI for the {@link MatsEagerCacheClient} and {@link MatsEagerCacheServer}.
 */
public interface MatsEagerCacheHtmlGui {
    /**
     * Factory method for creating an instance of {@link MatsEagerCacheHtmlGui} for the {@link MatsEagerCacheServer}.
     *
     * @param server
     *            the {@link MatsEagerCacheServer} instance to create the GUI for.
     * @return the {@link MatsEagerCacheHtmlGui} instance.
     */
    static MatsEagerCacheHtmlGui create(MatsEagerCacheServer server) {
        return new MatsEagerCacheServerHtmlGui(server);
    }

    /**
     * Factory method for creating an instance of {@link MatsEagerCacheHtmlGui} for the {@link MatsEagerCacheClient}.
     *
     * @param client
     *            the {@link MatsEagerCacheClient} instance to create the GUI for.
     * @return the {@link MatsEagerCacheHtmlGui} instance.
     */
    static MatsEagerCacheHtmlGui create(MatsEagerCacheClient client) {
        return new MatsEagerCacheClientHtmlGui(client);
    }

    /**
     * The maximum number of seconds to synchronously wait for a cache update to happen when requesting a refresh.
     */
    int MAX_WAIT_FOR_UPDATE_SECONDS = 20;

    /**
     * Note: The output from this method is static - and common between Client and Server - and it can be written
     * directly to the HTML page in a script-tag, or included as a separate file (with hard caching). It shall only be
     * included once even if there are several GUIs on the same page.
     */
    static void styleSheet(Appendable out) throws IOException {
        includeFile(out, "matseagercache.css");
    }

    /**
     * Note: The output from this method is static - and common between Client and Server - and it can be written
     * directly to the HTML page in a style-tag, or included as a separate file (with hard caching). It shall only be
     * included once even if there are several GUIs on the same page.
     */
    static void javaScript(Appendable out) throws IOException {
        includeFile(out, "matseagercache.js");
    }

    /**
     * The embeddable HTML GUI - map this to GET, content type is "text/html; charset=utf-8". This might
     * via the browser call back to {@link #json(Appendable, Map, String, AccessControl)} - which you also must mount at
     * (typically) the same URL (PUT, POST and DELETEs go there, GETs go here).
     */
    void html(Appendable out, Map requestParameters, AccessControl ac) throws IOException;

    /**
     * The HTML GUI will invoke JSON-over-HTTP to the same URL it is located at - map this to PUT, POST and DELETE,
     * returned content type shall be "application/json; charset=utf-8".
     * 

* NOTICE: If you have several GUIs on the same path, you must route them to the correct instance. This is done by * the URL parameters 'routingId' which will always be supplied by the GUI when doing operations - compare with the * {@link #getRoutingId()} of the different instances, and route to the one matching. *

* NOTICE: If you want to change the JSON Path, i.e. the path which this GUI employs to do "active" operations, you * can do so by prepending a little JavaScript section which sets the global variable "matsec_json_path" to the HTML * output, thus overriding the default which is to use the current URL path (i.e. the same as the GUI is served on). * They may be on the same path since the HTML is served using GET, while the JSON uses PUT, POST and DELETE with * header "Content-Type: application/json". */ void json(Appendable out, Map requestParameters, String requestBody, AccessControl ac) throws IOException; /** * @return the value which shall be compared when incoming {@link #json(Appendable, Map, String, AccessControl) * JSON-over-HTTP} requests must be routed to the correct instance. */ String getRoutingId(); /** * Interface which an instance of must be supplied to the {@link #html(Appendable, Map, AccessControl)} and * {@link #json(Appendable, Map, String, AccessControl)} methods, which allows the GUI log which user does some * operation, and to check if the user is allowed to do the operation. */ interface AccessControl { default String username() { return "{unknown}"; } default boolean acknowledgeException() { return false; } default boolean requestRefresh() { return false; } } /** * Quick way to get an {@link AccessControl} instance which allow all operations. * * @param username * the username to use in the {@link AccessControl#username()} method. * @return an {@link AccessControl} which allows all operations. */ static AccessControl getAccessControlAllowAll(String username) { return new AccessControl() { @Override public String username() { return username; } @Override public boolean acknowledgeException() { return true; } @Override public boolean requestRefresh() { return true; } }; } class AccessDeniedException extends RuntimeException { public AccessDeniedException(String message) { super(message); } } /** * Implementation of {@link MatsEagerCacheHtmlGui} for {@link MatsEagerCacheClient} - use the * {@link MatsEagerCacheHtmlGui#create(MatsEagerCacheClient)} factory method to get an instance. */ class MatsEagerCacheClientHtmlGui implements MatsEagerCacheHtmlGui { private static final Logger log = LoggerFactory.getLogger(MatsEagerCacheClientHtmlGui.class); private final MatsEagerCacheClient _client; private final String _routingId; final static ObjectReader JSON_READ_REQUEST = FieldBasedJacksonMapper .getMats3DefaultJacksonObjectMapper().readerFor(RequestDto.class); final static ObjectWriter JSON_WRITE_REPLY = FieldBasedJacksonMapper .getMats3DefaultJacksonObjectMapper().writerFor(ReplyDto.class); private MatsEagerCacheClientHtmlGui(MatsEagerCacheClient client) { _client = client; _routingId = "C-" + (client.getCacheClientInformation().getDataName() + "-" + client.getCacheClientInformation().getNodename()) .replaceAll("[^a-zA-Z0-9_]", "-"); } static void includeFile(Appendable out, String file) throws IOException { String filename = MatsEagerCacheHtmlGui.class.getPackage().getName().replace('.', '/') + '/' + file; InputStream is = MatsEagerCacheHtmlGui.class.getClassLoader().getResourceAsStream(filename); if (is == null) { throw new IllegalStateException("Missing '" + file + "' from ClassLoader."); } InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr); while (true) { String line = br.readLine(); if (line == null) { break; } out.append(line).append('\n'); } } @Override public void html(Appendable out, Map requestParameters, AccessControl ac) throws IOException { CacheClientInformation info = _client.getCacheClientInformation(); out.append("

\n"); out.append("

MatsEagerCacheClient ") .append(_client instanceof MatsEagerCacheClientMock ? "MOCK" : "") .append(" '").append(info.getDataName()).append("' @ '") .append(info.getNodename()).append("'

"); if (ac.requestRefresh()) { out.append("\n"); } out.append("
\n"); out.append("
").append(ac.username()).append("
\n"); if (_client instanceof MatsEagerCacheClientMock) { out.append( "
NOTICE! This is a MOCK of the MatsEagerCacheClient, and much of the information" + " below is mocked!

\n"); } out.append("
\n"); out.append("
\n"); out.append("DataName: ").append("").append(info.getDataName()).append("
\n"); out.append("Nodename: ").append("").append(info.getNodename()).append("
\n"); out.append("LifeCycle: ").append("").append(info.getCacheClientLifeCycle().toString()).append( "
\n"); out.append("CacheStarted: ") .append(_formatHtmlTimestamp(info.getCacheStartedTimestamp())).append("
\n"); out.append("BroadcastTopic: ").append("").append(info.getBroadcastTopic()).append("
\n"); out.append("InitialPopulationRequestSent: ") .append(_formatHtmlTimestamp(info.getInitialPopulationRequestSentTimestamp())).append("
\n"); boolean initialDone = info.isInitialPopulationDone(); long millisBetween = info.getInitialPopulationTimestamp() - info.getInitialPopulationRequestSentTimestamp(); out.append("InitialPopulationDone: ") .append(initialDone ? "Done @ " + _formatHtmlTimestamp(info.getInitialPopulationTimestamp()) + " - " + _formatMillis(millisBetween) + " after request" : "Waiting") .append("
\n"); out.append("LastFullUpdateReceived: ") .append(_formatHtmlTimestamp(info.getLastFullUpdateReceivedTimestamp())).append("
\n"); out.append("LastPartialUpdateReceived: ") .append(_formatHtmlTimestamp(info.getLastPartialUpdateReceivedTimestamp())).append("
\n"); out.append("
\n"); out.append("NumberOfFullUpdatesReceived: ").append("") .append(Integer.toString(info.getNumberOfFullUpdatesReceived())).append("
\n"); out.append("NumberOfPartialUpdatesReceived: ").append("") .append(Integer.toString(info.getNumberOfPartialUpdatesReceived())).append("
\n"); out.append("NumberOfAccesses: ").append("") .append(Long.toString(info.getNumberOfAccesses())).append("
\n"); out.append("
\n"); double lastRoundTripTimeMillis = info.getLastRoundTripTimeMillis(); out.append("LastRoundTripTime: ") .append(lastRoundTripTimeMillis > 0 ? _formatMillis(info.getLastRoundTripTimeMillis()) : "N/A - Click [Refresh]!") .append("   (Must chill ") .append(Integer.toString(MatsEagerCacheServer.FAST_RESPONSE_LAST_RECV_THRESHOLD_SECONDS)) .append(" seconds between [Refresh] to get precise timing)
\n"); out.append("
\n"); out.append("
\n"); // ?: Is the initial population done? if (!initialDone) { // -> No, initial population not done yet. out.append("  Initial population not done yet.

\n"); } else { // -> Yes, initial population done - show info. out.append("

Last Received Update


\n"); out.append("  Received: ") .append(_formatHtmlTimestamp(info.getLastAnyUpdateReceivedTimestamp())).append("
\n"); out.append("  Type: ").append("") .append(info.isLastUpdateFull() ? "Full" : "Partial").append("
\n"); out.append("  Mode: ").append("") .append(info.isLastUpdateLarge() ? "LARGE" : "Small").append("
\n"); out.append("  Server: ProduceAndCompressTotal: ") .append(_formatMillis(info.getLastUpdateProduceAndCompressTotalMillis())) .append("") .append(", of which compress: ") .append(_formatMillis(info.getLastUpdateCompressMillis())).append("") .append(", serialize: ") .append(_formatMillis(info.getLastUpdateSerializeMillis())).append("
\n"); out.append("  Client: DecompressAndConsumeTotal: ") .append(_formatMillis(info.getLastUpdateDecompressAndConsumeTotalMillis())) .append(", of which decompress: ") .append(_formatMillis(info.getLastUpdateDecompressMillis())).append("") .append(", deserialize: ") .append(_formatMillis(info.getLastUpdateDeserializeMillis())).append("
\n"); String meta = info.getLastUpdateMetadata(); out.append("  Metadata: ").append(meta != null ? "" + meta + "" : "none") .append("
\n"); out.append("  CompressedSize: ") .append(_formatHtmlBytes(info.getLastUpdateCompressedSize())).append("
\n"); out.append("  DecompressedSize: ") .append(_formatHtmlBytes(info.getLastUpdateDecompressedSize())).append("
\n"); out.append("  DataCount: ").append("") .append(Integer.toString(info.getLastUpdateDataCount())).append("
\n"); out.append("
\n"); out.append("  NOTE: If LARGE update, intentional lag is" + " added between nulling existing dataset and producing new to aid GC:" + " 100ms for Full, 25ms for Small.
\n"); } out.append("
\n"); out.append("Cache Servers: "); Map> appsNodes = info.getServerAppNamesToNodenames(); boolean first = true; for (Map.Entry> entry : appsNodes.entrySet()) { if (!first) { out.append(", "); } first = false; out.append("").append(entry.getKey()).append(": "); out.append(entry.getValue().stream().collect(Collectors.joining(", ", "", ". "))); } out.append("
\n"); out.append("
\n"); _logsAndExceptions(out, ac, getRoutingId(), info.getExceptionEntries(), info.getLogEntries()); out.append("
\n"); } @Override public void json(Appendable out, Map requestParameters, String requestBody, AccessControl ac) throws IOException { RequestDto dto = JSON_READ_REQUEST.readValue(requestBody); switch (dto.op) { case "requestRefresh": // # Access Check if (!ac.requestRefresh()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to request refresh."); } long nanosAsStart_request = System.nanoTime(); Optional cacheUpdated = _client.requestFullUpdate(MAX_WAIT_FOR_UPDATE_SECONDS * 1000); if (cacheUpdated.isPresent()) { double millisTaken_request = (System.nanoTime() - nanosAsStart_request) / 1_000_000d; out.append(new ReplyDto(true, "Refresh requested, got update, took " + _formatMillis(millisTaken_request) + ".").toJson()); } else { out.append(new ReplyDto(false, "Refresh requested, but timed out after waiting " + MAX_WAIT_FOR_UPDATE_SECONDS + " seconds.").toJson()); } break; case "acknowledgeSingle": // # Access Check if (!ac.acknowledgeException()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to acknowledge exceptions."); } boolean result = _client.getCacheClientInformation().acknowledgeException(dto.id, ac.username()); out.append(result ? new ReplyDto(true, "Acknowledged exception with id " + dto.id).toJson() : new ReplyDto(false, "Exception with id '" + dto.id + "' not found or" + " already acknowledged.").toJson()); break; case "acknowledgeUpTo": // # Access Check if (!ac.acknowledgeException()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to acknowledge exceptions."); } int numAcknowledged = _client.getCacheClientInformation() .acknowledgeExceptionsUpTo(dto.timestamp, ac.username()); out.append(new ReplyDto(true, "Acknowledged " + numAcknowledged + " exceptions up to" + " timestamp '" + _formatTimestamp(dto.timestamp) + "'").toJson()); break; default: throw new AccessDeniedException("User [" + ac.username() + "] tried to make an unknown operation [" + dto.op + "]."); } } @Override public String getRoutingId() { return _routingId; } static void _logsAndExceptions(Appendable out, AccessControl ac, String routingId, List exceptionEntries, List logEntries) throws IOException { out.append("
\n"); // -------------------------------------------------------------- // :: Print out exception entries, or "No exceptions" if none. if (exceptionEntries.isEmpty()) { out.append("

Exception entries


\n"); out.append("No exceptions!
\n"); } else { out.append("
"); out.append("

Exception entries

\n"); out.append(logEntries.size() > 5 ? "\n" : ""); // Count unacknowledged messages long unacknowledged = exceptionEntries.stream() .filter(e -> !e.isAcknowledged()) .count(); out.append(Integer.toString(exceptionEntries.size())) .append(exceptionEntries.size() != 1 ? " entries" : " entry") .append(" out of max " + CacheMonitor.MAX_ENTRIES + ", most recent first, "); if (unacknowledged > 0) { out.append("").append(Long.toString(unacknowledged)).append(" unacknowledged!\n"); } else { out.append("all acknowledged.\n"); } if (ac.acknowledgeException()) { out.append(" \n"); } out.append("" + "" + "" + "" + "" + ""); out.append("\n"); int count = 0; for (int i = exceptionEntries.size() - 1; i >= 0; i--) { ExceptionEntry entry = exceptionEntries.get(i); String ack = entry.isAcknowledged() ? "matsec-row-acknowledged" : "matsec-row-unacknowledged"; out.append(""); out.append(" "); out.append(" "); out.append(" "); out.append(" "); out.append("\n"); out.append("" : "'>"); out.append(" "); out.append("\n"); count++; } out.append("
TimestampCategoryMessageAcknowledged
") .append(_formatHtmlTimestamp(entry.getTimestamp())).append("") .append(entry.getCategory().toString()).append("") .append(entry.getMessage()).append("") .append(entry.isAcknowledged() ? _formatHtmlTimestamp(entry.getAcknowledgedTimestamp().getAsLong()) : ac.acknowledgeException() ? "" : "") .append(entry.isAcknowledged() ? " by " + entry.getAcknowledgedByUser().orElseThrow() : "") .append("
").append(entry.getThrowableAsString())
                            .append("
\n"); out.append("
\n"); } // -------------------------------------------------------------- // :: Print out log entries out.append("
\n"); out.append("
"); out.append("

Log entries

\n"); out.append(logEntries.size() > 5 ? "\n" : ""); out.append(Integer.toString(logEntries.size())).append(logEntries.size() != 1 ? " entries" : " entry") .append(" out of max " + CacheMonitor.MAX_ENTRIES + ", most recent first.
\n"); out.append("" + "" + "" + "" + "" + ""); out.append("\n"); int count = 0; for (int i = logEntries.size() - 1; i >= 0; i--) { LogEntry entry = logEntries.get(i); out.append("" : "'>"); out.append(""); out.append(""); out.append(""); out.append(""); out.append("\n"); count++; } out.append("
TimestampLevelCategoryMessage
").append(_formatHtmlTimestamp(entry.getTimestamp())) .append("").append(entry.getLevel().toString()).append("").append(entry.getCategory().toString()).append("").append(entry.getMessage().replace(" ##", "
")) .append("
\n"); out.append("
\n"); } static class RequestDto { String op; String id; long timestamp; } static class ReplyDto { boolean ok; String message; public ReplyDto(boolean ok, String message) { this.ok = ok; this.message = message; } public String toJson() { try { return MatsEagerCacheClientHtmlGui.JSON_WRITE_REPLY.writeValueAsString(this); } catch (JsonProcessingException e) { throw new RuntimeException("Could not serialize to JSON", e); } } } } /** * Implementation of {@link MatsEagerCacheHtmlGui} for {@link MatsEagerCacheServer} - use the * {@link MatsEagerCacheHtmlGui#create(MatsEagerCacheServer)} factory method to get an instance. */ class MatsEagerCacheServerHtmlGui implements MatsEagerCacheHtmlGui { private final MatsEagerCacheServer _server; private final String _routingId; private MatsEagerCacheServerHtmlGui(MatsEagerCacheServer server) { _server = server; _routingId = "S-" + (server.getCacheServerInformation().getDataName() + "-" + server.getCacheServerInformation().getNodename()) .replaceAll("[^a-zA-Z0-9_]", "-"); } @Override public void html(Appendable out, Map requestParameters, AccessControl ac) throws IOException { CacheServerInformation info = _server.getCacheServerInformation(); out.append("
\n"); out.append("

MatsEagerCacheServer '") .append(info.getDataName()).append("' @ '").append(info.getNodename()).append("'

"); if (ac.requestRefresh()) { out.append("\n"); } out.append("
\n"); out.append("
").append(ac.username()).append("
\n"); out.append("
\n"); out.append("
\n"); out.append("DataName: ").append("").append(info.getDataName()).append("
\n"); out.append("Nodename: ").append("").append(info.getNodename()).append("
\n"); out.append("LifeCycle: ").append("").append(info.getCacheServerLifeCycle().toString()).append( "
\n"); out.append("CacheStarted: ") .append(_formatHtmlTimestamp(info.getCacheStartedTimestamp())).append("
\n"); out.append("CacheRequestQueue: ").append("").append(info.getCacheRequestQueue()).append("
\n"); out.append("BroadcastTopic: ").append("").append(info.getBroadcastTopic()).append("
\n"); out.append("PeriodicFullUpdateIntervalMinutes: ").append("") .append(Double.toString(info.getPeriodicFullUpdateIntervalMinutes())).append("
\n"); out.append("
\n"); out.append("

Last Cluster Full Update


\n"); out.append("  RequestRegistered: ") .append(_formatHtmlTimestamp(info.getLastFullUpdateRequestRegisteredTimestamp())).append("
\n"); out.append("  ProductionStarted: ") .append(_formatHtmlTimestamp(info.getLastFullUpdateProductionStartedTimestamp())) .append("
\n"); out.append("  UpdateReceived: ") .append(_formatHtmlTimestamp(info.getLastFullUpdateReceivedTimestamp())).append("
\n"); out.append("  RegisterToUpdate: ") .append(info.getLastFullUpdateRegisterToUpdateMillis() == 0 ? "none" : "" + _formatMillis(info.getLastFullUpdateRegisterToUpdateMillis()) + "") .append("
\n"); out.append("
\n"); out.append("
\n"); out.append("
\n"); long lastUpdateSent = info.getLastUpdateSentTimestamp(); if (lastUpdateSent > 0) { out.append("

Last Update Produced on this Node


\n"); out.append("  Type: ").append("") .append(info.isLastUpdateFull() ? "Full" : "Partial").append("
\n"); out.append("  ProductionTotal: ") .append(_formatMillis(info.getLastUpdateProduceTotalMillis())) .append(" - source: ") .append(_formatMillis(info.getLastUpdateSourceMillis())) .append(", serialize: ") .append(_formatMillis(info.getLastUpdateSerializeMillis())) .append(", compress: ") .append(_formatMillis(info.getLastUpdateCompressMillis())) .append("
\n"); String meta = info.getLastUpdateMetadata(); out.append("  Metadata: ").append(meta != null ? "" + meta + "" : "none") .append("
\n"); out.append("  DataCount: ").append("") .append(Integer.toString(info.getLastUpdateDataCount())).append("
\n"); out.append("  UncompressedSize: ") .append(_formatHtmlBytes(info.getLastUpdateUncompressedSize())).append("
\n"); out.append("  CompressedSize: ") .append(_formatHtmlBytes(info.getLastUpdateCompressedSize())).append("
\n"); out.append("  Sent: ").append(_formatHtmlTimestamp(lastUpdateSent)).append("
\n"); out.append("
\n"); } else { out.append("No update sent yet.

\n"); } out.append("FullUpdates: ").append(Integer.toString(info.getNumberOfFullUpdatesSent())); out.append(" sent, out of ").append(Integer.toString(info.getNumberOfFullUpdatesReceived())) .append(" received.
\n"); out.append("PartialUpdates: ").append(Integer.toString(info.getNumberOfPartialUpdatesSent())); out.append(" sent, out of ").append(Integer.toString(info.getNumberOfPartialUpdatesReceived())) .append(" received.
\n"); out.append("
\n"); out.append("LastPartialUpdateReceived: ") .append(_formatHtmlTimestamp(info.getLastPartialUpdateReceivedTimestamp())).append("
\n"); out.append("
\n"); out.append("
\n"); out.append("Cache Servers: "); Map> appsNodes = info.getServerAppNamesToNodenames(); boolean first = true; for (Map.Entry> entry : appsNodes.entrySet()) { if (!first) { out.append(", "); } first = false; out.append("").append(entry.getKey()).append(": "); out.append(entry.getValue().stream().collect(Collectors.joining(", ", "(", ")"))); } out.append("
\n"); appsNodes = info.getClientAppNamesToNodenames(); out.append("Cache Client Apps (").append(Integer.toString(appsNodes.size())).append("): "); out.append(appsNodes.keySet().stream().collect(Collectors.joining(", ", "", ""))); out.append("
\n"); out.append("    Cache Client Nodes: "); first = true; for (Map.Entry> entry : appsNodes.entrySet()) { if (!first) { out.append(", "); } first = false; out.append("["); out.append(entry.getValue().stream().collect(Collectors.joining(", ", "", ""))); out.append("]"); } out.append("
(Reset when Cache Server nodes start, and at 04:00 - updated within 5 minutes after, and" + " every hour)

\n"); MatsEagerCacheClientHtmlGui._logsAndExceptions(out, ac, getRoutingId(), info.getExceptionEntries(), info .getLogEntries()); out.append("
\n"); } @Override public void json(Appendable out, Map requestParameters, String requestBody, AccessControl ac) throws IOException { RequestDto dto = MatsEagerCacheClientHtmlGui.JSON_READ_REQUEST.readValue(requestBody); switch (dto.op) { case "requestRefresh": // # Access Check if (!ac.requestRefresh()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to request refresh."); } long nanosAsStart_request = System.nanoTime(); boolean finished = _server.initiateFullUpdate( MAX_WAIT_FOR_UPDATE_SECONDS * 1000); if (finished) { double millisTaken_request = (System.nanoTime() - nanosAsStart_request) / 1_000_000d; out.append(new ReplyDto(true, "Refresh requested, got update, took " + _formatMillis(millisTaken_request) + ".").toJson()); } else { out.append(new ReplyDto(false, "Refresh requested, but timed out after waiting " + MAX_WAIT_FOR_UPDATE_SECONDS + " seconds.").toJson()); } break; case "acknowledgeSingle": // # Access Check if (!ac.acknowledgeException()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to acknowledge exceptions."); } boolean result = _server.getCacheServerInformation().acknowledgeException(dto.id, ac.username()); out.append(result ? new ReplyDto(true, "Acknowledged exception with id " + dto.id).toJson() : new ReplyDto(false, "Exception with id '" + dto.id + "' not found or" + " already acknowledged.").toJson()); break; case "acknowledgeUpTo": // # Access Check if (!ac.acknowledgeException()) { throw new AccessDeniedException("User [" + ac.username() + "] not allowed to acknowledge exceptions."); } int numAcknowledged = _server.getCacheServerInformation() .acknowledgeExceptionsUpTo(dto.timestamp, ac.username()); out.append(new ReplyDto(true, "Acknowledged " + numAcknowledged + " exceptions up to" + " timestamp '" + _formatTimestamp(dto.timestamp) + "'").toJson()); break; default: throw new AccessDeniedException("User [" + ac.username() + "] tried to make an unknown operation [" + dto.op + "]."); } } @Override public String getRoutingId() { return _routingId; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy