io.mats3.util.eagercache.MatsEagerCacheHtmlGui Maven / Gradle / Ivy
Show all versions of mats-util Show documentation
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(""
+ "Timestamp "
+ "Category "
+ "Message "
+ "Acknowledged "
+ " ");
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(" ")
.append(_formatHtmlTimestamp(entry.getTimestamp())).append(" ");
out.append(" ")
.append(entry.getCategory().toString()).append(" ");
out.append(" ");
out.append(" ")
.append(entry.isAcknowledged()
? _formatHtmlTimestamp(entry.getAcknowledgedTimestamp().getAsLong())
: ac.acknowledgeException()
? ""
: "")
.append(entry.isAcknowledged()
? " by " + entry.getAcknowledgedByUser().orElseThrow()
: "")
.append(" ");
out.append(" \n");
out.append("" : "'>");
out.append(" ").append(entry.getThrowableAsString())
.append("
");
out.append(" \n");
count++;
}
out.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(""
+ "Timestamp "
+ "Level "
+ "Category "
+ "Message "
+ " ");
out.append("\n");
int count = 0;
for (int i = logEntries.size() - 1; i >= 0; i--) {
LogEntry entry = logEntries.get(i);
out.append("" : "'>");
out.append("").append(_formatHtmlTimestamp(entry.getTimestamp()))
.append(" ");
out.append("").append(entry.getLevel().toString()).append(" ");
out.append("").append(entry.getCategory().toString()).append(" ");
out.append(" ");
out.append(" \n");
count++;
}
out.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;
}
}
}