From 0765c042f024e4043a067a51a896fd3ec284c94f Mon Sep 17 00:00:00 2001 From: maxstolze Date: Tue, 3 Mar 2020 15:12:35 +0100 Subject: [PATCH 1/6] Add ttl flag for validate --- .../java/won/bot/debugbot/impl/DebugBot.java | 687 ++++++++++-------- 1 file changed, 389 insertions(+), 298 deletions(-) diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index accd554..32576f4 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -102,9 +102,11 @@ import won.protocol.validation.WonConnectionValidator; /** - * Bot that reacts to each new atom that is created in the system by creating two atoms, it sends a connect message from - * one of these atoms, and a hint message for original atom offering match to another of these atoms. Additionally, it - * reacts to certain commands send via text messages on the connections with the created by the bot atoms. + * Bot that reacts to each new atom that is created in the system by creating + * two atoms, it sends a connect message from one of these atoms, and a hint + * message for original atom offering match to another of these atoms. + * Additionally, it reacts to certain commands send via text messages on the + * connections with the created by the bot atoms. */ public class DebugBot extends EventBot implements MatcherExtension, TextMessageCommandExtension, ServiceAtomExtension { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -135,302 +137,387 @@ public ServiceAtomBehaviour getServiceAtomBehaviour() { @Override protected void initializeEventListeners() { String welcomeMessage = "Greetings! I am the DebugBot. I " - + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " - + "type 'usage'."; + + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " + + "type 'usage'."; String welcomeHelpMessage = "When connecting with me, you can say 'ignore', or 'deny' to make me ignore or deny requests, and 'wait N' to make me wait N seconds (max 99) before reacting."; final EventListenerContext ctx = getEventListenerContext(); final EventBus bus = getEventBus(); // define BotCommands for TextMessageCommandBehaviour ArrayList botCommands = new ArrayList<>(); botCommands.add(new PatternMatcherTextMessageCommand("hint ((random|incompatible) socket)", - "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", - Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean socketHint = matcher.group(1) != null; - boolean incompatible = "incompatible".equals(matcher.group(3)); - boolean random = "random".equals(matcher.group(3)); - String hintType = socketHint ? incompatible ? "incompatible SocketHintMessage" - : random ? "random SocketHintMessage" : "SocketHintMessage" : "AtomHintMessage"; - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and send a " + hintType + " to you.")); - bus.publish(new HintDebugCommandEvent(connection, - socketHint - ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT - : random ? HintType.RANDOM_SOCKET_HINT : HintType.SOCKET_HINT - : HintType.ATOM_HINT)); - })); + "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", + Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", + Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean socketHint = matcher.group(1) != null; + boolean incompatible = "incompatible".equals(matcher.group(3)); + boolean random = "random".equals(matcher.group(3)); + String hintType = socketHint + ? incompatible ? "incompatible SocketHintMessage" + : random ? "random SocketHintMessage" + : "SocketHintMessage" + : "AtomHintMessage"; + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll create a new atom and send a " + hintType + + " to you.")); + bus.publish(new HintDebugCommandEvent(connection, socketHint + ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT + : random ? HintType.RANDOM_SOCKET_HINT + : HintType.SOCKET_HINT + : HintType.ATOM_HINT)); + })); botCommands.add(new EqualsTextMessageCommand("close", "close the current connection", "close", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll close this connection")); - bus.publish(new CloseCommandEvent(connection)); - })); - botCommands.add(new EqualsTextMessageCommand("modify", "modify the atom's description", "modify", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll change my atom description.")); - bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); - })); - botCommands.add(new PatternMatcherTextMessageCommand("connect", - "create a new atom and send connection request to it", - Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and make it send a connect to you.")); - bus.publish(new ConnectDebugCommandEvent(connection)); - })); - botCommands.add(new PatternMatcherTextMessageCommand("deactivate", - "deactivate remote atom of the current connection", - Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); - bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); - })); - botCommands.add(new PatternMatcherTextMessageCommand("chatty (on|off)", - "send chat messages spontaneously every now and then? (default: on)", - Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("on".equals(param)) { + (Connection connection) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you messages spontaneously from time to time.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, true)); - } else if ("off".equals(param)) { + "Ok, I'll close this connection")); + bus.publish(new CloseCommandEvent(connection)); + })); + botCommands.add(new EqualsTextMessageCommand("modify", "modify the atom's description", "modify", + (Connection connection) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, from now on I will be quiet and only respond to your messages.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, false)); - } - } - })); - botCommands.add(new PatternMatcherTextMessageCommand("cache (eager|lazy)", "use lazy or eager RDF cache", - Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("eager".equals(param)) { + "Ok, I'll change my atom description.")); + bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); + })); + botCommands.add(new PatternMatcherTextMessageCommand("connect", + "create a new atom and send connection request to it", + Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); - bus.publish(new SetCacheEagernessCommandEvent(true)); - } else if ("lazy".equals(param)) { + "Ok, I'll create a new atom and make it send a connect to you.")); + bus.publish(new ConnectDebugCommandEvent(connection)); + })); + botCommands.add(new PatternMatcherTextMessageCommand("deactivate", + "deactivate remote atom of the current connection", + Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); - bus.publish(new SetCacheEagernessCommandEvent(false)); - } - } - })); - botCommands.add(new PatternMatcherTextMessageCommand("send N", - "send N messages, one per second. N must be an integer between 1 and 9", - Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.find(); - String nStr = matcher.group(1); - int n = Integer.parseInt(nStr); - bus.publish(new SendNDebugCommandEvent(connection, n)); - })); - botCommands.add(new PatternMatcherTextMessageCommand("validate", "download the connection data and validate it", - Pattern.compile("^validate$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); - // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(connection.getAtomURI(), - connection.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, - command, Duration.ofSeconds(60)); - final StopWatch crawlStopWatch = new StopWatch(); - crawlStopWatch.start("crawl"); - crawlConnectionDataBehaviour - .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, crawlStopWatch)); - crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { - @Override - protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { - try { - logger.debug("validating data of connection {}", command.getConnectionURI()); - // TODO: use one validator for all invocations - WonConnectionValidator validator = new WonConnectionValidator(); - StringBuilder message = new StringBuilder(); - boolean valid = validator.validate(successEvent.getCrawledData(), message); - String successMessage = "Connection " + command.getConnectionURI() + " is valid: " - + valid + " " + message.toString(); - return WonRdfUtils.MessageUtils.textMessage(successMessage); - } catch (Exception e) { - return WonRdfUtils.MessageUtils.textMessage("Caught exception during validation: " + e); + "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); + bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); + })); + botCommands.add(new PatternMatcherTextMessageCommand("chatty (on|off)", + "send chat messages spontaneously every now and then? (default: on)", + Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("on".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you messages spontaneously from time to time.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, + true)); + } else if ("off".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, from now on I will be quiet and only respond to your messages.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, + false)); + } } - } - }); - crawlConnectionDataBehaviour.activate(); - })); - botCommands.add(new PatternMatcherTextMessageCommand("retract (mine|proposal)", - "retract the last (proposal) message you sent, or the last message I sent", - Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(3) != null; - boolean retractProposes = matcher.group(4) != null; - String whose = useWrongSender ? "your" : "my"; - String which = retractProposes ? "proposal " : ""; - referToEarlierMessages(ctx, bus, connection, - "ok, I'll retract " + whose + " latest " + which - + "message - but 'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getNthLatestMessage(m -> retractProposes - ? (m.isProposesMessage() || m.isProposesToCancelMessage()) - && m.getEffects().stream().anyMatch(MessageEffect::isProposes) - : useWrongSender ? m.getSenderAtomURI().equals(connection.getTargetAtomURI()) - : m.getSenderAtomURI().equals(connection.getAtomURI()), - 0); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRetracts, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot retract any messages - I did not find any."; + })); + botCommands.add(new PatternMatcherTextMessageCommand("cache (eager|lazy)", + "use lazy or eager RDF cache", + Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("eager".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); + bus.publish(new SetCacheEagernessCommandEvent(true)); + } else if ("lazy".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); + bus.publish(new SetCacheEagernessCommandEvent(false)); } - Optional retractedString = state.getTextMessage(uris[0]); - String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby retracting " + whose + " message" + finalRetractedString - + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " - + getDurationString(queryDuration) + " seconds."; - }); - })); + } + })); + botCommands.add(new PatternMatcherTextMessageCommand("send N", + "send N messages, one per second. N must be an integer between 1 and 9", + Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.find(); + String nStr = matcher.group(1); + int n = Integer.parseInt(nStr); + bus.publish(new SendNDebugCommandEvent(connection, n)); + })); + botCommands.add(new PatternMatcherTextMessageCommand("validate (ttl)", + "download the connection data and validate it", + Pattern.compile("^validate(\\s+(ttl))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); + // initiate crawl behaviour + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent( + connection.getAtomURI(), connection.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour( + ctx, command, Duration.ofSeconds(60)); + final StopWatch crawlStopWatch = new StopWatch(); + crawlStopWatch.start("crawl"); + crawlConnectionDataBehaviour.onResult(new SendMessageReportingCrawlResultAction( + ctx, connection, crawlStopWatch)); + crawlConnectionDataBehaviour + .onResult(new SendMessageOnCrawlResultAction(ctx, connection) { + @Override + protected Model makeSuccessMessage( + CrawlConnectionCommandSuccessEvent successEvent) { + try { + logger.debug("validating data of connection {}", + command.getConnectionURI()); + // TODO: use one validator for all + // invocations + WonConnectionValidator validator = new WonConnectionValidator(); + StringBuilder message = new StringBuilder(); + boolean valid = validator.validate( + successEvent.getCrawledData(), + message); + if (matcher.matches()) { + String param = matcher.group(2); + if ("ttl".equals(param)) { + // add ttl as file + // attachment to message + } + } + String successMessage = "Connection " + + command.getConnectionURI() + + " is valid: " + valid + + " " + + message.toString(); + return WonRdfUtils.MessageUtils + .textMessage(successMessage); + } catch (Exception e) { + return WonRdfUtils.MessageUtils + .textMessage("Caught exception during validation: " + + e); + } + } + }); + crawlConnectionDataBehaviour.activate(); + })); + botCommands.add(new PatternMatcherTextMessageCommand("retract (mine|proposal)", + "retract the last (proposal) message you sent, or the last message I sent", + Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(3) != null; + boolean retractProposes = matcher.group(4) != null; + String whose = useWrongSender ? "your" : "my"; + String which = retractProposes ? "proposal " : ""; + referToEarlierMessages(ctx, bus, connection, "ok, I'll retract " + whose + + " latest " + which + + "message - but 'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getNthLatestMessage(m -> retractProposes + ? (m.isProposesMessage() || m + .isProposesToCancelMessage()) + && m.getEffects() + .stream() + .anyMatch(MessageEffect::isProposes) + : useWrongSender ? m.getSenderAtomURI() + .equals(connection + .getTargetAtomURI()) + : m.getSenderAtomURI() + .equals(connection + .getAtomURI()), + 0); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRetracts, + (Duration queryDuration, AgreementProtocolState state, + URI... uris) -> { + if (uris == null || uris.length == 0 + || uris[0] == null) { + return "Sorry, I cannot retract any messages - I did not find any."; + } + Optional retractedString = state + .getTextMessage(uris[0]); + String finalRetractedString = retractedString + .map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby retracting " + whose + + " message" + finalRetractedString + + " (uri: " + uris[0] + ")." + + "\n The query for finding that message took " + + getDurationString(queryDuration) + + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("reject (yours)", - "reject the last rejectable message I (you) sent", - Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(2) != null; - String whose = useWrongSender ? "my" : "your"; - referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose - + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( - useWrongSender ? connection.getAtomURI() : connection.getTargetAtomURI()); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRejects, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot reject any of " + whose - + " messages - I did not find any suitable message."; - } - Optional retractedString = state.getTextMessage(uris[0]); - String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby rejecting " + whose + " message" + finalRetractedString - + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " - + getDurationString(queryDuration) + " seconds."; - }); - })); + "reject the last rejectable message I (you) sent", + Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(2) != null; + String whose = useWrongSender ? "my" : "your"; + referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose + + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( + useWrongSender ? connection.getAtomURI() + : connection.getTargetAtomURI()); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRejects, + (Duration queryDuration, AgreementProtocolState state, + URI... uris) -> { + if (uris == null || uris.length == 0 + || uris[0] == null) { + return "Sorry, I cannot reject any of " + whose + + " messages - I did not find any suitable message."; + } + Optional retractedString = state + .getTextMessage(uris[0]); + String finalRetractedString = retractedString + .map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby rejecting " + whose + " message" + + finalRetractedString + " (uri: " + + uris[0] + ")." + + "\n The query for finding that message took " + + getDurationString(queryDuration) + + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("propose (my|any) (N)", - "propose one (N, max 9) of my(/your/any) messages for an agreement", - Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean my = matcher.group(3) != null; - boolean any = matcher.group(4) != null; - int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); - boolean allowOwnClauses = any || !my; - boolean allowCounterpartClauses = any || my; - String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" : allowCounterpartClauses - ? "your" : " - sorry, don't know which ones to choose, actually - "; - referToEarlierMessages(ctx, bus, connection, "ok, I'll make a proposal containing " + count + " of " - + whose - + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", - state -> state.getNLatestMessageUris(m -> { - URI ownedAtomUri = connection.getAtomURI(); - URI targetAtomUri = connection.getTargetAtomURI(); - return ownedAtomUri != null && ownedAtomUri.equals(m.getSenderAtomURI()) - && allowOwnClauses - || targetAtomUri != null && targetAtomUri.equals(m.getSenderAtomURI()) - && allowCounterpartClauses; - }, count + 1).subList(1, count + 1), WonRdfUtils.MessageUtils::addProposes, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot propose the messages - I did not find any."; - } - // Optional proposedString = - // state.getTextMessage(uris[0]); - return "Ok, I am hereby making the proposal, containing " + uris.length + " clauses." - + "\n The query for finding the clauses took " - + getDurationString(queryDuration) + " seconds."; - }); - })); + "propose one (N, max 9) of my(/your/any) messages for an agreement", + Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean my = matcher.group(3) != null; + boolean any = matcher.group(4) != null; + int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); + boolean allowOwnClauses = any || !my; + boolean allowCounterpartClauses = any || my; + String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" + : allowCounterpartClauses ? "your" + : " - sorry, don't know which ones to choose, actually - "; + referToEarlierMessages(ctx, bus, connection, + "ok, I'll make a proposal containing " + count + " of " + whose + + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", + state -> state.getNLatestMessageUris(m -> { + URI ownedAtomUri = connection.getAtomURI(); + URI targetAtomUri = connection.getTargetAtomURI(); + return ownedAtomUri != null + && ownedAtomUri.equals( + m.getSenderAtomURI()) + && allowOwnClauses + || targetAtomUri != null + && targetAtomUri.equals( + m.getSenderAtomURI()) + && allowCounterpartClauses; + }, count + 1).subList(1, count + 1), + WonRdfUtils.MessageUtils::addProposes, (Duration queryDuration, + AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 + || uris[0] == null) { + return "Sorry, I cannot propose the messages - I did not find any."; + } + // Optional proposedString = + // state.getTextMessage(uris[0]); + return "Ok, I am hereby making the proposal, containing " + + uris.length + " clauses." + + "\n The query for finding the clauses took " + + getDurationString(queryDuration) + + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("accept", - "accept the last proposal/claim made (including cancellation proposals)", - Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestPendingProposalOrClaim(Optional.empty(), - Optional.of(connection.getTargetAtomURI())); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addAccepts, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot accept any proposal - I did not find pending proposals"; - } - return "Ok, I am hereby accepting your latest proposal (uri: " + uris[0] + ")." - + "\n The query for finding it took " + getDurationString(queryDuration) - + " seconds."; - }))); + "accept the last proposal/claim made (including cancellation proposals)", + Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestPendingProposalOrClaim( + Optional.empty(), + Optional.of(connection.getTargetAtomURI())); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addAccepts, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot accept any proposal - I did not find pending proposals"; + } + return "Ok, I am hereby accepting your latest proposal (uri: " + + uris[0] + ")." + + "\n The query for finding it took " + + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("cancel", - "propose to cancel the newest agreement (that wasn't only a cancellation)", - Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestAgreement(); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addProposesToCancel, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null || state == null) { - return "Sorry, I cannot propose to cancel any agreement - I did not find any"; - } - return "Ok, I am hereby proposing to cancel our latest agreement (uri: " + uris[0] + ")." - + "\n The query for finding it took " + getDurationString(queryDuration) - + " seconds."; - }))); + "propose to cancel the newest agreement (that wasn't only a cancellation)", + Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestAgreement(); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addProposesToCancel, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null + || state == null) { + return "Sorry, I cannot propose to cancel any agreement - I did not find any"; + } + return "Ok, I am hereby proposing to cancel our latest agreement (uri: " + + uris[0] + ")." + + "\n The query for finding it took " + + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("inject", - "send a message in this connection that will be forwarded to all other connections we have", - Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); - // build a message to be injected into all connections of the receiver atom - // (not - // controlled by us) - Model messageModel = WonRdfUtils.MessageUtils.textMessage("This is the injected message."); - // the atom whose connections we want to inject into - URI targetAtom = connection.getTargetAtomURI(); - // we iterate over our atoms and see which of them are connected to the - // remote - // atom - Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); - Set targetConnections = myatoms.stream() - // don't inject into the current connection - .filter(uri -> !connection.getAtomURI().equals(uri)).map(uri -> { - // for each of my (the bot's) atoms, check if they are - // connected to the remote - // atom of the current conversation - Dataset atomNetwork = WonLinkedDataUtils.getConnectionNetwork(uri, - ctx.getLinkedDataSource()); - return WonRdfUtils.AtomUtils.getTargetConnectionURIsForTargetAtoms(atomNetwork, - Collections.singletonList(targetAtom), Optional.of(ConnectionState.CONNECTED)); - }).flatMap(Collection::stream).collect(Collectors.toSet()); - bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, targetConnections)); - })); + "send a message in this connection that will be forwarded to all other connections we have", + Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); + // build a message to be injected into all connections of the receiver atom + // (not + // controlled by us) + Model messageModel = WonRdfUtils.MessageUtils + .textMessage("This is the injected message."); + // the atom whose connections we want to inject into + URI targetAtom = connection.getTargetAtomURI(); + // we iterate over our atoms and see which of them are connected to the + // remote + // atom + Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); + Set targetConnections = myatoms.stream() + // don't inject into the current connection + .filter(uri -> !connection.getAtomURI().equals(uri)) + .map(uri -> { + // for each of my (the bot's) atoms, check if they are + // connected to the remote + // atom of the current conversation + Dataset atomNetwork = WonLinkedDataUtils + .getConnectionNetwork(uri, ctx + .getLinkedDataSource()); + return WonRdfUtils.AtomUtils + .getTargetConnectionURIsForTargetAtoms( + atomNetwork, + Collections.singletonList( + targetAtom), + Optional.of(ConnectionState.CONNECTED)); + }).flatMap(Collection::stream).collect(Collectors.toSet()); + bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, + targetConnections)); + })); // activate ServiceAtomBehaviour serviceAtomBehaviour = new ServiceAtomBehaviour(ctx); serviceAtomBehaviour.activate(); // activate TextMessageCommandBehaviour textMessageCommandBehaviour = new TextMessageCommandBehaviour(ctx, - botCommands.toArray(new TextMessageCommand[0])); + botCommands.toArray(new TextMessageCommand[0])); textMessageCommandBehaviour.activate(); // eagerly cache RDF data BotBehaviour eagerlyCacheBehaviour = new EagerlyPopulateCacheBehaviour(ctx); eagerlyCacheBehaviour.activate(); // register listeners for event.impl.command events used to tell the bot to send // messages - ExecuteWonMessageCommandBehaviour wonMessageCommandBehaviour = new ExecuteWonMessageCommandBehaviour(ctx); + ExecuteWonMessageCommandBehaviour wonMessageCommandBehaviour = new ExecuteWonMessageCommandBehaviour( + ctx); wonMessageCommandBehaviour.activate(); // set up matching extension - matcherBehaviour = new MatcherBehaviour(ctx, "DebugBotMatchingExtension", registrationMatcherRetryInterval); + matcherBehaviour = new MatcherBehaviour(ctx, "DebugBotMatchingExtension", + registrationMatcherRetryInterval); matcherBehaviour.activate(); // filter to prevent reacting to own atoms NotFilter noOwnAtomsFilter = getNoOwnAtomsFilter(); @@ -447,53 +534,55 @@ protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEve // as soon as the echo atom triggered by debug connect created, connect to // original bus.subscribe(AtomCreatedEventForDebugConnect.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), welcomeMessage + " " + welcomeHelpMessage))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), + welcomeMessage + " " + welcomeHelpMessage))); // as soon as the echo atom triggered by debug hint command created, hint to // original bus.subscribe(AtomCreatedEventForDebugHint.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), matcherUri))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), matcherUri))); // if the original atom wants to connect - always open bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), - new PublishSetChattinessEventAction(ctx, true)); + new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), + new PublishSetChattinessEventAction(ctx, true)); // if the remote side opens, send a greeting and set to chatty. bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new PublishSetChattinessEventAction(ctx, true)); + new PublishSetChattinessEventAction(ctx, true)); // filter to prevent reacting to message Commands NotFilter noTextMessageCommandsFilter = getNoTextMessageCommandFilter(); bus.subscribe(ConnectFromOtherAtomEvent.class, - new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), - new DebugBotIncomingGenericMessageAction(ctx)); + new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), + new DebugBotIncomingGenericMessageAction(ctx)); // if the bot receives a text message - try to map the command of the text // message to a DebugEvent bus.subscribe(MessageFromOtherAtomEvent.class, noTextMessageCommandsFilter, - new DebugBotIncomingGenericMessageAction(ctx)); + new DebugBotIncomingGenericMessageAction(ctx)); bus.subscribe(CloseCommandSuccessEvent.class, new PublishSetChattinessEventAction(ctx, false)); // react to close event: set connection to not chatty bus.subscribe(CloseFromOtherAtomEvent.class, new PublishSetChattinessEventAction(ctx, false)); MessageTimingManager timingManager = new MessageTimingManager(ctx); // on every actEvent there is a chance we send a chatty message bus.subscribe(ActEvent.class, - new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, - DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, - DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); + new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, + DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, + DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); // process eliza messages with eliza bus.subscribe(MessageToElizaEvent.class, new AnswerWithElizaAction(ctx)); // remember when we sent the last message - bus.subscribe(WonMessageSentOnConnectionEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); + bus.subscribe(WonMessageSentOnConnectionEvent.class, + new RecordMessageSentTimeAction(ctx, timingManager)); // remember when we got the last message bus.subscribe(WonMessageReceivedOnConnectionEvent.class, - new RecordMessageReceivedTimeAction(ctx, timingManager)); + new RecordMessageReceivedTimeAction(ctx, timingManager)); // initialize the sent timestamp when the connect message is received bus.subscribe(ConnectFromOtherAtomEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); // Usage Command Event Subscriptions: bus.subscribe(ReplaceDebugAtomContentCommandEvent.class, new ReplaceDebugAtomContentAction(ctx)); bus.subscribe(SendNDebugCommandEvent.class, new SendNDebugMessagesAction(ctx, DELAY_BETWEEN_N_MESSAGES, - DebugBotIncomingGenericMessageAction.N_MESSAGES)); + DebugBotIncomingGenericMessageAction.N_MESSAGES)); // react to the hint and connect commands by creating an atom (it will fire // correct atom created for connect/hint // events) @@ -518,7 +607,8 @@ protected void doRun(Event event, EventListener executingListener) throws Except } /*********************************************************************************** - * Mini framework for allowing the bot to refer to earlier messages while trying to avoid code duplication + * Mini framework for allowing the bot to refer to earlier messages while trying + * to avoid code duplication ***********************************************************************************/ private interface MessageFinder { List findMessages(AgreementProtocolState state); @@ -533,30 +623,31 @@ private interface TextMessageMaker { } private void referToEarlierMessages(EventListenerContext ctx, EventBus bus, Connection con, - String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, - TextMessageMaker textMessageMaker) { + String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, + TextMessageMaker textMessageMaker) { bus.publish(new ConnectionMessageCommandEvent(con, crawlAnnouncement)); // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(con.getAtomURI(), con.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, command, - Duration.ofSeconds(60)); + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(con.getAtomURI(), + con.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, + command, Duration.ofSeconds(60)); final StopWatch crawlStopWatch = new StopWatch(); crawlStopWatch.start("crawl"); AgreementProtocolState state = WonConversationUtils.getAgreementProtocolState(con.getConnectionURI(), - ctx.getLinkedDataSource()); + ctx.getLinkedDataSource()); crawlStopWatch.stop(); Duration crawlDuration = Duration.ofMillis(crawlStopWatch.getLastTaskTimeMillis()); - getEventListenerContext().getEventBus() - .publish(new ConnectionMessageCommandEvent(con, + getEventListenerContext().getEventBus().publish(new ConnectionMessageCommandEvent(con, "Finished crawl in " + getDurationString(crawlDuration) + " seconds. The dataset has " - + state.getConversationDataset().asDatasetGraph().size() + " rdf graphs.")); + + state.getConversationDataset().asDatasetGraph().size() + + " rdf graphs.")); Model messageModel = makeReferringMessage(state, messageFinder, messageReferrer, textMessageMaker); getEventListenerContext().getEventBus().publish(new ConnectionMessageCommandEvent(con, messageModel)); crawlConnectionDataBehaviour.activate(); } private Model makeReferringMessage(AgreementProtocolState state, MessageFinder messageFinder, - MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { + MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { int origPrio = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); StopWatch queryStopWatch = new StopWatch(); @@ -567,7 +658,7 @@ private Model makeReferringMessage(AgreementProtocolState state, MessageFinder m Thread.currentThread().setPriority(origPrio); Duration queryDuration = Duration.ofMillis(queryStopWatch.getLastTaskTimeMillis()); Model messageModel = WonRdfUtils.MessageUtils - .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); + .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); return messageReferrer.referToMessages(messageModel, targetUriArray); } From bf0eda903e53afd49a5e0380bd717539fa6ba160 Mon Sep 17 00:00:00 2001 From: maxstolze Date: Wed, 4 Mar 2020 11:16:58 +0100 Subject: [PATCH 2/6] Sending conversation data as file after validation --- .gitignore | 5 ++- pom.xml | 2 +- .../java/won/bot/debugbot/impl/DebugBot.java | 43 ++++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index ac80349..e900c00 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ bot-keys.jks owner-trusted-certs.jks /target/ /.idea/ -*.iml \ No newline at end of file +*.iml + +# validation files +conversationData-*.txt \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4665f85..1e4430d 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ at.researchstudio.sat won-bot - 0.9 + 0.10-SNAPSHOT diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index 32576f4..eb44d26 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -10,13 +10,18 @@ */ package won.bot.debugbot.impl; +import java.io.File; +import java.io.FileWriter; import java.lang.invoke.MethodHandles; import java.net.URI; +import java.nio.file.Files; import java.text.DecimalFormat; import java.time.Duration; import java.util.ArrayList; +import java.util.Base64; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Set; @@ -96,6 +101,7 @@ import won.protocol.model.Connection; import won.protocol.model.ConnectionState; import won.protocol.model.SocketType; +import won.protocol.util.RdfUtils; import won.protocol.util.WonConversationUtils; import won.protocol.util.WonRdfUtils; import won.protocol.util.linkeddata.WonLinkedDataUtils; @@ -240,9 +246,9 @@ protected void initializeEventListeners() { int n = Integer.parseInt(nStr); bus.publish(new SendNDebugCommandEvent(connection, n)); })); - botCommands.add(new PatternMatcherTextMessageCommand("validate (ttl)", + botCommands.add(new PatternMatcherTextMessageCommand("validate (attach)", "download the connection data and validate it", - Pattern.compile("^validate(\\s+(ttl))?$", Pattern.CASE_INSENSITIVE), + Pattern.compile("^validate(\\s+(attach))?$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { bus.publish(new ConnectionMessageCommandEvent(connection, "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); @@ -270,20 +276,35 @@ protected Model makeSuccessMessage( boolean valid = validator.validate( successEvent.getCrawledData(), message); - if (matcher.matches()) { - String param = matcher.group(2); - if ("ttl".equals(param)) { - // add ttl as file - // attachment to message - } - } String successMessage = "Connection " + command.getConnectionURI() + " is valid: " + valid + " " + message.toString(); - return WonRdfUtils.MessageUtils - .textMessage(successMessage); + if (matcher.matches()) { + String param = matcher.group(2); + if ("attach".equals(param)) { + // add data as file + // attachment to message + String dataSetInput = RdfUtils + .toString(successEvent + .getCrawledData()); + Date date = new Date(); + File file = new File("conversationData-" + + date.getTime() + ".txt"); + FileWriter writer = new FileWriter(file); + writer.write(dataSetInput); + writer.close(); + byte[] fileContent = Files.readAllBytes(file.toPath()); + String encodedString = Base64.getEncoder() + .encodeToString(fileContent); + return WonRdfUtils.MessageUtils + .fileMessage(encodedString, + file.getName(), + successMessage); + } + } + return WonRdfUtils.MessageUtils.textMessage(successMessage); } catch (Exception e) { return WonRdfUtils.MessageUtils .textMessage("Caught exception during validation: " From 04ca1828ccdf08c9ef85fbfe9f812f45e6485a82 Mon Sep 17 00:00:00 2001 From: maxstolze Date: Mon, 9 Mar 2020 10:46:28 +0100 Subject: [PATCH 3/6] Send trig file without saving to file system --- src/main/java/won/bot/debugbot/impl/DebugBot.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index eb44d26..e4cd2ff 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -10,11 +10,8 @@ */ package won.bot.debugbot.impl; -import java.io.File; -import java.io.FileWriter; import java.lang.invoke.MethodHandles; import java.net.URI; -import java.nio.file.Files; import java.text.DecimalFormat; import java.time.Duration; import java.util.ArrayList; @@ -290,17 +287,15 @@ protected Model makeSuccessMessage( .toString(successEvent .getCrawledData()); Date date = new Date(); - File file = new File("conversationData-" - + date.getTime() + ".txt"); - FileWriter writer = new FileWriter(file); - writer.write(dataSetInput); - writer.close(); - byte[] fileContent = Files.readAllBytes(file.toPath()); + String fileName = "conversationData-" + date.getTime() + + ".trig"; + byte[] fileContent = dataSetInput.getBytes("UTF-8"); String encodedString = Base64.getEncoder() .encodeToString(fileContent); return WonRdfUtils.MessageUtils .fileMessage(encodedString, - file.getName(), + fileName, + "application/trig", successMessage); } } From b12b58fa5bd32052c7935ba3619443e163da2f1f Mon Sep 17 00:00:00 2001 From: maxstolze Date: Mon, 9 Mar 2020 17:19:13 +0100 Subject: [PATCH 4/6] Adding dataset returns for agrrement data --- .../java/won/bot/debugbot/impl/DebugBot.java | 766 +++++++++--------- 1 file changed, 374 insertions(+), 392 deletions(-) diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index e4cd2ff..004af9c 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -140,400 +140,384 @@ public ServiceAtomBehaviour getServiceAtomBehaviour() { @Override protected void initializeEventListeners() { String welcomeMessage = "Greetings! I am the DebugBot. I " - + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " - + "type 'usage'."; + + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " + + "type 'usage'."; String welcomeHelpMessage = "When connecting with me, you can say 'ignore', or 'deny' to make me ignore or deny requests, and 'wait N' to make me wait N seconds (max 99) before reacting."; final EventListenerContext ctx = getEventListenerContext(); final EventBus bus = getEventBus(); // define BotCommands for TextMessageCommandBehaviour ArrayList botCommands = new ArrayList<>(); botCommands.add(new PatternMatcherTextMessageCommand("hint ((random|incompatible) socket)", - "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", - Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", - Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean socketHint = matcher.group(1) != null; - boolean incompatible = "incompatible".equals(matcher.group(3)); - boolean random = "random".equals(matcher.group(3)); - String hintType = socketHint - ? incompatible ? "incompatible SocketHintMessage" - : random ? "random SocketHintMessage" - : "SocketHintMessage" - : "AtomHintMessage"; - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and send a " + hintType - + " to you.")); - bus.publish(new HintDebugCommandEvent(connection, socketHint - ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT - : random ? HintType.RANDOM_SOCKET_HINT - : HintType.SOCKET_HINT - : HintType.ATOM_HINT)); - })); + "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", + Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean socketHint = matcher.group(1) != null; + boolean incompatible = "incompatible".equals(matcher.group(3)); + boolean random = "random".equals(matcher.group(3)); + String hintType = socketHint + ? incompatible ? "incompatible SocketHintMessage" + : random ? "random SocketHintMessage" : "SocketHintMessage" + : "AtomHintMessage"; + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll create a new atom and send a " + hintType + " to you.")); + bus.publish(new HintDebugCommandEvent(connection, + socketHint + ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT + : random ? HintType.RANDOM_SOCKET_HINT : HintType.SOCKET_HINT + : HintType.ATOM_HINT)); + })); botCommands.add(new EqualsTextMessageCommand("close", "close the current connection", "close", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll close this connection")); - bus.publish(new CloseCommandEvent(connection)); - })); + (Connection connection) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll close this connection")); + bus.publish(new CloseCommandEvent(connection)); + })); botCommands.add(new EqualsTextMessageCommand("modify", "modify the atom's description", "modify", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll change my atom description.")); - bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); - })); + (Connection connection) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll change my atom description.")); + bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); + })); botCommands.add(new PatternMatcherTextMessageCommand("connect", - "create a new atom and send connection request to it", - Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and make it send a connect to you.")); - bus.publish(new ConnectDebugCommandEvent(connection)); - })); + "create a new atom and send connection request to it", + Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll create a new atom and make it send a connect to you.")); + bus.publish(new ConnectDebugCommandEvent(connection)); + })); botCommands.add(new PatternMatcherTextMessageCommand("deactivate", - "deactivate remote atom of the current connection", - Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); - bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); - })); + "deactivate remote atom of the current connection", + Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); + bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); + })); botCommands.add(new PatternMatcherTextMessageCommand("chatty (on|off)", - "send chat messages spontaneously every now and then? (default: on)", - Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("on".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you messages spontaneously from time to time.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, - true)); - } else if ("off".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, from now on I will be quiet and only respond to your messages.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, - false)); + "send chat messages spontaneously every now and then? (default: on)", + Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("on".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you messages spontaneously from time to time.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, true)); + } else if ("off".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, from now on I will be quiet and only respond to your messages.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, false)); + } + } + })); + botCommands.add(new PatternMatcherTextMessageCommand("cache (eager|lazy)", "use lazy or eager RDF cache", + Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("eager".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); + bus.publish(new SetCacheEagernessCommandEvent(true)); + } else if ("lazy".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); + bus.publish(new SetCacheEagernessCommandEvent(false)); + } + } + })); + botCommands.add(new PatternMatcherTextMessageCommand("send N", + "send N messages, one per second. N must be an integer between 1 and 9", + Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.find(); + String nStr = matcher.group(1); + int n = Integer.parseInt(nStr); + bus.publish(new SendNDebugCommandEvent(connection, n)); + })); + botCommands.add(new PatternMatcherTextMessageCommand("validate (attach)", + "download the connection data and validate it", + Pattern.compile("^validate(\\s+(attach))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); + // initiate crawl behaviour + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(connection.getAtomURI(), + connection.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, + command, Duration.ofSeconds(60)); + final StopWatch crawlStopWatch = new StopWatch(); + crawlStopWatch.start("crawl"); + crawlConnectionDataBehaviour + .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, crawlStopWatch)); + crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { + @Override + protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { + try { + logger.debug("validating data of connection {}", command.getConnectionURI()); + // TODO: use one validator for all + // invocations + WonConnectionValidator validator = new WonConnectionValidator(); + StringBuilder message = new StringBuilder(); + boolean valid = validator.validate(successEvent.getCrawledData(), message); + String successMessage = "Connection " + command.getConnectionURI() + " is valid: " + + valid + " " + message.toString(); + if (matcher.matches()) { + String param = matcher.group(2); + if ("attach".equals(param)) { + // add data as file + // attachment to message + String dataSetInput = RdfUtils.toString(successEvent.getCrawledData()); + Date date = new Date(); + String fileName = "conversationData-" + date.getTime() + ".trig"; + byte[] fileContent = dataSetInput.getBytes("UTF-8"); + String encodedString = Base64.getEncoder().encodeToString(fileContent); + return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, + "application/trig", successMessage); + } } + return WonRdfUtils.MessageUtils.textMessage(successMessage); + } catch (Exception e) { + return WonRdfUtils.MessageUtils.textMessage("Caught exception during validation: " + e); } - })); - botCommands.add(new PatternMatcherTextMessageCommand("cache (eager|lazy)", - "use lazy or eager RDF cache", - Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("eager".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); - bus.publish(new SetCacheEagernessCommandEvent(true)); - } else if ("lazy".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); - bus.publish(new SetCacheEagernessCommandEvent(false)); + } + }); + crawlConnectionDataBehaviour.activate(); + })); + botCommands.add(new PatternMatcherTextMessageCommand("send dataset (agreements|claims|proposals)", + "download the connection data and returns dataset for (agreements|claims|proposals)", + Pattern.compile("^send dataset(\\s+((agreements)|(claims)|(proposals)))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "ok, I'll return the dataset - but I'll need to crawl the connection data first, please be patient.")); + // initiate crawl behaviour + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(connection.getAtomURI(), + connection.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, + command, Duration.ofSeconds(60)); + final StopWatch crawlStopWatch = new StopWatch(); + crawlStopWatch.start("crawl"); + crawlConnectionDataBehaviour + .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, crawlStopWatch)); + crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { + @Override + protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { + try { + logger.debug("validating data of connection {}", command.getConnectionURI()); + WonConnectionValidator validator = new WonConnectionValidator(); + StringBuilder message = new StringBuilder(); + boolean valid = validator.validate(successEvent.getCrawledData(), message); + String successMessage = "Connection " + command.getConnectionURI() + " is valid: " + + valid + " " + message.toString(); + if (matcher.matches()) { + Dataset dataSet = successEvent.getCrawledData(); + AgreementProtocolState agreementState = AgreementProtocolState.of(dataSet); + String dataSetString = new String(); + String filePrefix = new String(); + String param = matcher.group(2); + if ("agreements".equals(param)) { + // add data as file + // attachment to message + filePrefix = "agreementData"; + dataSetString = RdfUtils.toString(agreementState.getAgreements()); + } else if ("proposals".equals(param)) { + filePrefix = "proposalData"; + dataSetString = RdfUtils.toString(agreementState.getPendingProposals()); + } else if ("claims".equals(param)) { + filePrefix = "claimsData"; + dataSetString = RdfUtils.toString(agreementState.getClaims()); + } else { + return WonRdfUtils.MessageUtils + .textMessage("Caught exception during dataset retrieval"); + } + Date date = new Date(); + String fileName = filePrefix + "-" + date.getTime() + ".trig"; + byte[] fileContent = dataSetString.getBytes("UTF-8"); + String encodedString = Base64.getEncoder().encodeToString(fileContent); + return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, + "application/trig", successMessage); } + + return WonRdfUtils.MessageUtils.textMessage(successMessage); + } catch (Exception e) { + return WonRdfUtils.MessageUtils.textMessage("Caught exception during validation: " + e); } - })); - botCommands.add(new PatternMatcherTextMessageCommand("send N", - "send N messages, one per second. N must be an integer between 1 and 9", - Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.find(); - String nStr = matcher.group(1); - int n = Integer.parseInt(nStr); - bus.publish(new SendNDebugCommandEvent(connection, n)); - })); - botCommands.add(new PatternMatcherTextMessageCommand("validate (attach)", - "download the connection data and validate it", - Pattern.compile("^validate(\\s+(attach))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); - // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent( - connection.getAtomURI(), connection.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour( - ctx, command, Duration.ofSeconds(60)); - final StopWatch crawlStopWatch = new StopWatch(); - crawlStopWatch.start("crawl"); - crawlConnectionDataBehaviour.onResult(new SendMessageReportingCrawlResultAction( - ctx, connection, crawlStopWatch)); - crawlConnectionDataBehaviour - .onResult(new SendMessageOnCrawlResultAction(ctx, connection) { - @Override - protected Model makeSuccessMessage( - CrawlConnectionCommandSuccessEvent successEvent) { - try { - logger.debug("validating data of connection {}", - command.getConnectionURI()); - // TODO: use one validator for all - // invocations - WonConnectionValidator validator = new WonConnectionValidator(); - StringBuilder message = new StringBuilder(); - boolean valid = validator.validate( - successEvent.getCrawledData(), - message); - String successMessage = "Connection " - + command.getConnectionURI() - + " is valid: " + valid - + " " - + message.toString(); - if (matcher.matches()) { - String param = matcher.group(2); - if ("attach".equals(param)) { - // add data as file - // attachment to message - String dataSetInput = RdfUtils - .toString(successEvent - .getCrawledData()); - Date date = new Date(); - String fileName = "conversationData-" + date.getTime() - + ".trig"; - byte[] fileContent = dataSetInput.getBytes("UTF-8"); - String encodedString = Base64.getEncoder() - .encodeToString(fileContent); - return WonRdfUtils.MessageUtils - .fileMessage(encodedString, - fileName, - "application/trig", - successMessage); - } - } - return WonRdfUtils.MessageUtils.textMessage(successMessage); - } catch (Exception e) { - return WonRdfUtils.MessageUtils - .textMessage("Caught exception during validation: " - + e); - } - } - }); - crawlConnectionDataBehaviour.activate(); - })); + } + }); + crawlConnectionDataBehaviour.activate(); + })); botCommands.add(new PatternMatcherTextMessageCommand("retract (mine|proposal)", - "retract the last (proposal) message you sent, or the last message I sent", - Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(3) != null; - boolean retractProposes = matcher.group(4) != null; - String whose = useWrongSender ? "your" : "my"; - String which = retractProposes ? "proposal " : ""; - referToEarlierMessages(ctx, bus, connection, "ok, I'll retract " + whose - + " latest " + which - + "message - but 'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getNthLatestMessage(m -> retractProposes - ? (m.isProposesMessage() || m - .isProposesToCancelMessage()) - && m.getEffects() - .stream() - .anyMatch(MessageEffect::isProposes) - : useWrongSender ? m.getSenderAtomURI() - .equals(connection - .getTargetAtomURI()) - : m.getSenderAtomURI() - .equals(connection - .getAtomURI()), - 0); - return uri == null ? Collections.EMPTY_LIST - : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRetracts, - (Duration queryDuration, AgreementProtocolState state, - URI... uris) -> { - if (uris == null || uris.length == 0 - || uris[0] == null) { - return "Sorry, I cannot retract any messages - I did not find any."; - } - Optional retractedString = state - .getTextMessage(uris[0]); - String finalRetractedString = retractedString - .map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby retracting " + whose - + " message" + finalRetractedString - + " (uri: " + uris[0] + ")." - + "\n The query for finding that message took " - + getDurationString(queryDuration) - + " seconds."; - }); - })); + "retract the last (proposal) message you sent, or the last message I sent", + Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(3) != null; + boolean retractProposes = matcher.group(4) != null; + String whose = useWrongSender ? "your" : "my"; + String which = retractProposes ? "proposal " : ""; + referToEarlierMessages(ctx, bus, connection, + "ok, I'll retract " + whose + " latest " + which + + "message - but 'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getNthLatestMessage(m -> retractProposes + ? (m.isProposesMessage() || m.isProposesToCancelMessage()) + && m.getEffects().stream().anyMatch(MessageEffect::isProposes) + : useWrongSender ? m.getSenderAtomURI().equals(connection.getTargetAtomURI()) + : m.getSenderAtomURI().equals(connection.getAtomURI()), + 0); + return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRetracts, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot retract any messages - I did not find any."; + } + Optional retractedString = state.getTextMessage(uris[0]); + String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby retracting " + whose + " message" + finalRetractedString + + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("reject (yours)", - "reject the last rejectable message I (you) sent", - Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(2) != null; - String whose = useWrongSender ? "my" : "your"; - referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose - + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( - useWrongSender ? connection.getAtomURI() - : connection.getTargetAtomURI()); - return uri == null ? Collections.EMPTY_LIST - : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRejects, - (Duration queryDuration, AgreementProtocolState state, - URI... uris) -> { - if (uris == null || uris.length == 0 - || uris[0] == null) { - return "Sorry, I cannot reject any of " + whose - + " messages - I did not find any suitable message."; - } - Optional retractedString = state - .getTextMessage(uris[0]); - String finalRetractedString = retractedString - .map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby rejecting " + whose + " message" - + finalRetractedString + " (uri: " - + uris[0] + ")." - + "\n The query for finding that message took " - + getDurationString(queryDuration) - + " seconds."; - }); - })); + "reject the last rejectable message I (you) sent", + Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(2) != null; + String whose = useWrongSender ? "my" : "your"; + referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose + + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( + useWrongSender ? connection.getAtomURI() : connection.getTargetAtomURI()); + return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRejects, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot reject any of " + whose + + " messages - I did not find any suitable message."; + } + Optional retractedString = state.getTextMessage(uris[0]); + String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby rejecting " + whose + " message" + finalRetractedString + + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("propose (my|any) (N)", - "propose one (N, max 9) of my(/your/any) messages for an agreement", - Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean my = matcher.group(3) != null; - boolean any = matcher.group(4) != null; - int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); - boolean allowOwnClauses = any || !my; - boolean allowCounterpartClauses = any || my; - String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" - : allowCounterpartClauses ? "your" - : " - sorry, don't know which ones to choose, actually - "; - referToEarlierMessages(ctx, bus, connection, - "ok, I'll make a proposal containing " + count + " of " + whose - + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", - state -> state.getNLatestMessageUris(m -> { - URI ownedAtomUri = connection.getAtomURI(); - URI targetAtomUri = connection.getTargetAtomURI(); - return ownedAtomUri != null - && ownedAtomUri.equals( - m.getSenderAtomURI()) - && allowOwnClauses - || targetAtomUri != null - && targetAtomUri.equals( - m.getSenderAtomURI()) - && allowCounterpartClauses; - }, count + 1).subList(1, count + 1), - WonRdfUtils.MessageUtils::addProposes, (Duration queryDuration, - AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 - || uris[0] == null) { - return "Sorry, I cannot propose the messages - I did not find any."; - } - // Optional proposedString = - // state.getTextMessage(uris[0]); - return "Ok, I am hereby making the proposal, containing " - + uris.length + " clauses." - + "\n The query for finding the clauses took " - + getDurationString(queryDuration) - + " seconds."; - }); - })); + "propose one (N, max 9) of my(/your/any) messages for an agreement", + Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean my = matcher.group(3) != null; + boolean any = matcher.group(4) != null; + int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); + boolean allowOwnClauses = any || !my; + boolean allowCounterpartClauses = any || my; + String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" + : allowCounterpartClauses ? "your" + : " - sorry, don't know which ones to choose, actually - "; + referToEarlierMessages(ctx, bus, connection, "ok, I'll make a proposal containing " + count + " of " + + whose + + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", + state -> state.getNLatestMessageUris(m -> { + URI ownedAtomUri = connection.getAtomURI(); + URI targetAtomUri = connection.getTargetAtomURI(); + return ownedAtomUri != null && ownedAtomUri.equals(m.getSenderAtomURI()) + && allowOwnClauses + || targetAtomUri != null && targetAtomUri.equals(m.getSenderAtomURI()) + && allowCounterpartClauses; + }, count + 1).subList(1, count + 1), WonRdfUtils.MessageUtils::addProposes, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot propose the messages - I did not find any."; + } + // Optional proposedString = + // state.getTextMessage(uris[0]); + return "Ok, I am hereby making the proposal, containing " + uris.length + " clauses." + + "\n The query for finding the clauses took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("accept", - "accept the last proposal/claim made (including cancellation proposals)", - Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestPendingProposalOrClaim( - Optional.empty(), - Optional.of(connection.getTargetAtomURI())); - return uri == null ? Collections.EMPTY_LIST - : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addAccepts, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot accept any proposal - I did not find pending proposals"; - } - return "Ok, I am hereby accepting your latest proposal (uri: " - + uris[0] + ")." - + "\n The query for finding it took " - + getDurationString(queryDuration) - + " seconds."; - }))); + "accept the last proposal/claim made (including cancellation proposals)", + Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestPendingProposalOrClaim(Optional.empty(), + Optional.of(connection.getTargetAtomURI())); + return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addAccepts, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot accept any proposal - I did not find pending proposals"; + } + return "Ok, I am hereby accepting your latest proposal (uri: " + uris[0] + ")." + + "\n The query for finding it took " + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("cancel", - "propose to cancel the newest agreement (that wasn't only a cancellation)", - Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestAgreement(); - return uri == null ? Collections.EMPTY_LIST - : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addProposesToCancel, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null - || state == null) { - return "Sorry, I cannot propose to cancel any agreement - I did not find any"; - } - return "Ok, I am hereby proposing to cancel our latest agreement (uri: " - + uris[0] + ")." - + "\n The query for finding it took " - + getDurationString(queryDuration) - + " seconds."; - }))); + "propose to cancel the newest agreement (that wasn't only a cancellation)", + Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestAgreement(); + return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addProposesToCancel, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null || state == null) { + return "Sorry, I cannot propose to cancel any agreement - I did not find any"; + } + return "Ok, I am hereby proposing to cancel our latest agreement (uri: " + uris[0] + ")." + + "\n The query for finding it took " + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("inject", - "send a message in this connection that will be forwarded to all other connections we have", - Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); - // build a message to be injected into all connections of the receiver atom - // (not - // controlled by us) - Model messageModel = WonRdfUtils.MessageUtils - .textMessage("This is the injected message."); - // the atom whose connections we want to inject into - URI targetAtom = connection.getTargetAtomURI(); - // we iterate over our atoms and see which of them are connected to the - // remote - // atom - Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); - Set targetConnections = myatoms.stream() - // don't inject into the current connection - .filter(uri -> !connection.getAtomURI().equals(uri)) - .map(uri -> { - // for each of my (the bot's) atoms, check if they are - // connected to the remote - // atom of the current conversation - Dataset atomNetwork = WonLinkedDataUtils - .getConnectionNetwork(uri, ctx - .getLinkedDataSource()); - return WonRdfUtils.AtomUtils - .getTargetConnectionURIsForTargetAtoms( - atomNetwork, - Collections.singletonList( - targetAtom), - Optional.of(ConnectionState.CONNECTED)); - }).flatMap(Collection::stream).collect(Collectors.toSet()); - bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, - targetConnections)); - })); + "send a message in this connection that will be forwarded to all other connections we have", + Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); + // build a message to be injected into all connections of the receiver atom + // (not + // controlled by us) + Model messageModel = WonRdfUtils.MessageUtils.textMessage("This is the injected message."); + // the atom whose connections we want to inject into + URI targetAtom = connection.getTargetAtomURI(); + // we iterate over our atoms and see which of them are connected to the + // remote + // atom + Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); + Set targetConnections = myatoms.stream() + // don't inject into the current connection + .filter(uri -> !connection.getAtomURI().equals(uri)).map(uri -> { + // for each of my (the bot's) atoms, check if they are + // connected to the remote + // atom of the current conversation + Dataset atomNetwork = WonLinkedDataUtils.getConnectionNetwork(uri, + ctx.getLinkedDataSource()); + return WonRdfUtils.AtomUtils.getTargetConnectionURIsForTargetAtoms(atomNetwork, + Collections.singletonList(targetAtom), Optional.of(ConnectionState.CONNECTED)); + }).flatMap(Collection::stream).collect(Collectors.toSet()); + bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, targetConnections)); + })); // activate ServiceAtomBehaviour serviceAtomBehaviour = new ServiceAtomBehaviour(ctx); serviceAtomBehaviour.activate(); // activate TextMessageCommandBehaviour textMessageCommandBehaviour = new TextMessageCommandBehaviour(ctx, - botCommands.toArray(new TextMessageCommand[0])); + botCommands.toArray(new TextMessageCommand[0])); textMessageCommandBehaviour.activate(); // eagerly cache RDF data BotBehaviour eagerlyCacheBehaviour = new EagerlyPopulateCacheBehaviour(ctx); eagerlyCacheBehaviour.activate(); // register listeners for event.impl.command events used to tell the bot to send // messages - ExecuteWonMessageCommandBehaviour wonMessageCommandBehaviour = new ExecuteWonMessageCommandBehaviour( - ctx); + ExecuteWonMessageCommandBehaviour wonMessageCommandBehaviour = new ExecuteWonMessageCommandBehaviour(ctx); wonMessageCommandBehaviour.activate(); // set up matching extension - matcherBehaviour = new MatcherBehaviour(ctx, "DebugBotMatchingExtension", - registrationMatcherRetryInterval); + matcherBehaviour = new MatcherBehaviour(ctx, "DebugBotMatchingExtension", registrationMatcherRetryInterval); matcherBehaviour.activate(); // filter to prevent reacting to own atoms NotFilter noOwnAtomsFilter = getNoOwnAtomsFilter(); @@ -550,55 +534,53 @@ protected Model makeSuccessMessage( // as soon as the echo atom triggered by debug connect created, connect to // original bus.subscribe(AtomCreatedEventForDebugConnect.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), - welcomeMessage + " " + welcomeHelpMessage))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), welcomeMessage + " " + welcomeHelpMessage))); // as soon as the echo atom triggered by debug hint command created, hint to // original bus.subscribe(AtomCreatedEventForDebugHint.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), matcherUri))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), matcherUri))); // if the original atom wants to connect - always open bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), - new PublishSetChattinessEventAction(ctx, true)); + new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), + new PublishSetChattinessEventAction(ctx, true)); // if the remote side opens, send a greeting and set to chatty. bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new PublishSetChattinessEventAction(ctx, true)); + new PublishSetChattinessEventAction(ctx, true)); // filter to prevent reacting to message Commands NotFilter noTextMessageCommandsFilter = getNoTextMessageCommandFilter(); bus.subscribe(ConnectFromOtherAtomEvent.class, - new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), - new DebugBotIncomingGenericMessageAction(ctx)); + new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), + new DebugBotIncomingGenericMessageAction(ctx)); // if the bot receives a text message - try to map the command of the text // message to a DebugEvent bus.subscribe(MessageFromOtherAtomEvent.class, noTextMessageCommandsFilter, - new DebugBotIncomingGenericMessageAction(ctx)); + new DebugBotIncomingGenericMessageAction(ctx)); bus.subscribe(CloseCommandSuccessEvent.class, new PublishSetChattinessEventAction(ctx, false)); // react to close event: set connection to not chatty bus.subscribe(CloseFromOtherAtomEvent.class, new PublishSetChattinessEventAction(ctx, false)); MessageTimingManager timingManager = new MessageTimingManager(ctx); // on every actEvent there is a chance we send a chatty message bus.subscribe(ActEvent.class, - new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, - DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, - DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); + new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, + DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, + DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); // process eliza messages with eliza bus.subscribe(MessageToElizaEvent.class, new AnswerWithElizaAction(ctx)); // remember when we sent the last message - bus.subscribe(WonMessageSentOnConnectionEvent.class, - new RecordMessageSentTimeAction(ctx, timingManager)); + bus.subscribe(WonMessageSentOnConnectionEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); // remember when we got the last message bus.subscribe(WonMessageReceivedOnConnectionEvent.class, - new RecordMessageReceivedTimeAction(ctx, timingManager)); + new RecordMessageReceivedTimeAction(ctx, timingManager)); // initialize the sent timestamp when the connect message is received bus.subscribe(ConnectFromOtherAtomEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); // Usage Command Event Subscriptions: bus.subscribe(ReplaceDebugAtomContentCommandEvent.class, new ReplaceDebugAtomContentAction(ctx)); bus.subscribe(SendNDebugCommandEvent.class, new SendNDebugMessagesAction(ctx, DELAY_BETWEEN_N_MESSAGES, - DebugBotIncomingGenericMessageAction.N_MESSAGES)); + DebugBotIncomingGenericMessageAction.N_MESSAGES)); // react to the hint and connect commands by creating an atom (it will fire // correct atom created for connect/hint // events) @@ -609,6 +591,7 @@ protected Model makeSuccessMessage( bus.subscribe(SetChattinessDebugCommandEvent.class, new SetChattinessAction(ctx)); // react to a bot command activating/deactivating eager caching bus.subscribe(SetCacheEagernessCommandEvent.class, new BaseEventBotAction(ctx) { + @Override protected void doRun(Event event, EventListener executingListener) throws Exception { if (event instanceof SetCacheEagernessCommandEvent) { @@ -639,31 +622,30 @@ private interface TextMessageMaker { } private void referToEarlierMessages(EventListenerContext ctx, EventBus bus, Connection con, - String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, - TextMessageMaker textMessageMaker) { + String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, + TextMessageMaker textMessageMaker) { bus.publish(new ConnectionMessageCommandEvent(con, crawlAnnouncement)); // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(con.getAtomURI(), - con.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, - command, Duration.ofSeconds(60)); + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(con.getAtomURI(), con.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, command, + Duration.ofSeconds(60)); final StopWatch crawlStopWatch = new StopWatch(); crawlStopWatch.start("crawl"); AgreementProtocolState state = WonConversationUtils.getAgreementProtocolState(con.getConnectionURI(), - ctx.getLinkedDataSource()); + ctx.getLinkedDataSource()); crawlStopWatch.stop(); Duration crawlDuration = Duration.ofMillis(crawlStopWatch.getLastTaskTimeMillis()); - getEventListenerContext().getEventBus().publish(new ConnectionMessageCommandEvent(con, + getEventListenerContext().getEventBus() + .publish(new ConnectionMessageCommandEvent(con, "Finished crawl in " + getDurationString(crawlDuration) + " seconds. The dataset has " - + state.getConversationDataset().asDatasetGraph().size() - + " rdf graphs.")); + + state.getConversationDataset().asDatasetGraph().size() + " rdf graphs.")); Model messageModel = makeReferringMessage(state, messageFinder, messageReferrer, textMessageMaker); getEventListenerContext().getEventBus().publish(new ConnectionMessageCommandEvent(con, messageModel)); crawlConnectionDataBehaviour.activate(); } private Model makeReferringMessage(AgreementProtocolState state, MessageFinder messageFinder, - MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { + MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { int origPrio = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); StopWatch queryStopWatch = new StopWatch(); @@ -674,7 +656,7 @@ private Model makeReferringMessage(AgreementProtocolState state, MessageFinder m Thread.currentThread().setPriority(origPrio); Duration queryDuration = Duration.ofMillis(queryStopWatch.getLastTaskTimeMillis()); Model messageModel = WonRdfUtils.MessageUtils - .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); + .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); return messageReferrer.referToMessages(messageModel, targetUriArray); } From b8a92940f1d7a006ac10c6b788122c61c5437bcb Mon Sep 17 00:00:00 2001 From: maxstolze Date: Tue, 10 Mar 2020 10:19:48 +0100 Subject: [PATCH 5/6] Return agreement, proposal & claim dataset --- .../java/won/bot/debugbot/impl/DebugBot.java | 768 +++++++++--------- 1 file changed, 404 insertions(+), 364 deletions(-) diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index 004af9c..d4bae3e 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -140,374 +140,412 @@ public ServiceAtomBehaviour getServiceAtomBehaviour() { @Override protected void initializeEventListeners() { String welcomeMessage = "Greetings! I am the DebugBot. I " - + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " - + "type 'usage'."; + + "can simulate multiple other users so you can test things. I understand a few commands. To see which ones, " + + "type 'usage'."; String welcomeHelpMessage = "When connecting with me, you can say 'ignore', or 'deny' to make me ignore or deny requests, and 'wait N' to make me wait N seconds (max 99) before reacting."; final EventListenerContext ctx = getEventListenerContext(); final EventBus bus = getEventBus(); // define BotCommands for TextMessageCommandBehaviour ArrayList botCommands = new ArrayList<>(); botCommands.add(new PatternMatcherTextMessageCommand("hint ((random|incompatible) socket)", - "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", - Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean socketHint = matcher.group(1) != null; - boolean incompatible = "incompatible".equals(matcher.group(3)); - boolean random = "random".equals(matcher.group(3)); - String hintType = socketHint - ? incompatible ? "incompatible SocketHintMessage" - : random ? "random SocketHintMessage" : "SocketHintMessage" - : "AtomHintMessage"; - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and send a " + hintType + " to you.")); - bus.publish(new HintDebugCommandEvent(connection, - socketHint - ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT - : random ? HintType.RANDOM_SOCKET_HINT : HintType.SOCKET_HINT - : HintType.ATOM_HINT)); - })); + "create a new atom and send me an atom or socket hint (between random or incompatible sockets)", + Pattern.compile("^hint(\\s+((random|incompatible)\\s+)?socket)?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean socketHint = matcher.group(1) != null; + boolean incompatible = "incompatible".equals(matcher.group(3)); + boolean random = "random".equals(matcher.group(3)); + String hintType = socketHint + ? incompatible ? "incompatible SocketHintMessage" + : random ? "random SocketHintMessage" : "SocketHintMessage" + : "AtomHintMessage"; + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll create a new atom and send a " + hintType + " to you.")); + bus.publish(new HintDebugCommandEvent(connection, + socketHint + ? incompatible ? HintType.INCOMPATIBLE_SOCKET_HINT + : random ? HintType.RANDOM_SOCKET_HINT + : HintType.SOCKET_HINT + : HintType.ATOM_HINT)); + })); botCommands.add(new EqualsTextMessageCommand("close", "close the current connection", "close", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll close this connection")); - bus.publish(new CloseCommandEvent(connection)); - })); + (Connection connection) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll close this connection")); + bus.publish(new CloseCommandEvent(connection)); + })); botCommands.add(new EqualsTextMessageCommand("modify", "modify the atom's description", "modify", - (Connection connection) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, "Ok, I'll change my atom description.")); - bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); - })); + (Connection connection) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll change my atom description.")); + bus.publish(new ReplaceDebugAtomContentCommandEvent(connection)); + })); botCommands.add(new PatternMatcherTextMessageCommand("connect", - "create a new atom and send connection request to it", - Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll create a new atom and make it send a connect to you.")); - bus.publish(new ConnectDebugCommandEvent(connection)); - })); - botCommands.add(new PatternMatcherTextMessageCommand("deactivate", - "deactivate remote atom of the current connection", - Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); - bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); - })); - botCommands.add(new PatternMatcherTextMessageCommand("chatty (on|off)", - "send chat messages spontaneously every now and then? (default: on)", - Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("on".equals(param)) { + "create a new atom and send connection request to it", + Pattern.compile("^connect$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you messages spontaneously from time to time.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, true)); - } else if ("off".equals(param)) { + "Ok, I'll create a new atom and make it send a connect to you.")); + bus.publish(new ConnectDebugCommandEvent(connection)); + })); + botCommands.add(new PatternMatcherTextMessageCommand("deactivate", + "deactivate remote atom of the current connection", + Pattern.compile("^deactivate$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, from now on I will be quiet and only respond to your messages.")); - bus.publish(new SetChattinessDebugCommandEvent(connection, false)); - } - } - })); + "Ok, I'll deactivate this atom. This will close the connection we are currently talking on.")); + bus.publish(new DeactivateAtomCommandEvent(connection.getAtomURI())); + })); + botCommands.add(new PatternMatcherTextMessageCommand("chatty (on|off)", + "send chat messages spontaneously every now and then? (default: on)", + Pattern.compile("^chatty(\\s+(on|off))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("on".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you messages spontaneously from time to time.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, true)); + } else if ("off".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, from now on I will be quiet and only respond to your messages.")); + bus.publish(new SetChattinessDebugCommandEvent(connection, false)); + } + } + })); botCommands.add(new PatternMatcherTextMessageCommand("cache (eager|lazy)", "use lazy or eager RDF cache", - Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - if (matcher.matches()) { - String param = matcher.group(2); - if ("eager".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); - bus.publish(new SetCacheEagernessCommandEvent(true)); - } else if ("lazy".equals(param)) { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); - bus.publish(new SetCacheEagernessCommandEvent(false)); - } - } - })); + Pattern.compile("^cache(\\s+(eager|lazy))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + if (matcher.matches()) { + String param = matcher.group(2); + if ("eager".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll put any message I receive or send into the RDF cache. This slows down message processing in general, but operations that require crawling connection data will be faster.")); + bus.publish(new SetCacheEagernessCommandEvent(true)); + } else if ("lazy".equals(param)) { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I won't put messages I receive or send into the RDF cache. This speeds up message processing in general, but operations that require crawling connection data will be slowed down.")); + bus.publish(new SetCacheEagernessCommandEvent(false)); + } + } + })); botCommands.add(new PatternMatcherTextMessageCommand("send N", - "send N messages, one per second. N must be an integer between 1 and 9", - Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.find(); - String nStr = matcher.group(1); - int n = Integer.parseInt(nStr); - bus.publish(new SendNDebugCommandEvent(connection, n)); - })); + "send N messages, one per second. N must be an integer between 1 and 9", + Pattern.compile("^send ([1-9])$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.find(); + String nStr = matcher.group(1); + int n = Integer.parseInt(nStr); + bus.publish(new SendNDebugCommandEvent(connection, n)); + })); botCommands.add(new PatternMatcherTextMessageCommand("validate (attach)", - "download the connection data and validate it", - Pattern.compile("^validate(\\s+(attach))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); - // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(connection.getAtomURI(), - connection.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, - command, Duration.ofSeconds(60)); - final StopWatch crawlStopWatch = new StopWatch(); - crawlStopWatch.start("crawl"); - crawlConnectionDataBehaviour - .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, crawlStopWatch)); - crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { - @Override - protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { - try { - logger.debug("validating data of connection {}", command.getConnectionURI()); - // TODO: use one validator for all - // invocations - WonConnectionValidator validator = new WonConnectionValidator(); - StringBuilder message = new StringBuilder(); - boolean valid = validator.validate(successEvent.getCrawledData(), message); - String successMessage = "Connection " + command.getConnectionURI() + " is valid: " - + valid + " " + message.toString(); - if (matcher.matches()) { - String param = matcher.group(2); - if ("attach".equals(param)) { - // add data as file - // attachment to message - String dataSetInput = RdfUtils.toString(successEvent.getCrawledData()); - Date date = new Date(); - String fileName = "conversationData-" + date.getTime() + ".trig"; - byte[] fileContent = dataSetInput.getBytes("UTF-8"); - String encodedString = Base64.getEncoder().encodeToString(fileContent); - return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, - "application/trig", successMessage); + "download the connection data and validate it", + Pattern.compile("^validate(\\s+(attach))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "ok, I'll validate the connection - but I'll need to crawl the connection data first, please be patient.")); + // initiate crawl behaviour + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent( + connection.getAtomURI(), + connection.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour( + ctx, + command, Duration.ofSeconds(60)); + final StopWatch crawlStopWatch = new StopWatch(); + crawlStopWatch.start("crawl"); + crawlConnectionDataBehaviour + .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, + crawlStopWatch)); + crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { + @Override + protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { + try { + logger.debug("validating data of connection {}", command.getConnectionURI()); + // TODO: use one validator for all + // invocations + WonConnectionValidator validator = new WonConnectionValidator(); + StringBuilder message = new StringBuilder(); + boolean valid = validator.validate(successEvent.getCrawledData(), message); + String successMessage = "Connection " + command.getConnectionURI() + + " is valid: " + + valid + " " + message.toString(); + if (matcher.matches()) { + String param = matcher.group(2); + if ("attach".equals(param)) { + // add data as file + // attachment to message + String dataSetInput = RdfUtils.toString(successEvent.getCrawledData()); + Date date = new Date(); + String fileName = "conversationData-" + date.getTime() + ".trig"; + byte[] fileContent = dataSetInput.getBytes("UTF-8"); + String encodedString = Base64.getEncoder().encodeToString(fileContent); + return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, + "application/trig", successMessage); + } + } + return WonRdfUtils.MessageUtils.textMessage(successMessage); + } catch (Exception e) { + return WonRdfUtils.MessageUtils + .textMessage("Caught exception during validation: " + e); } } - return WonRdfUtils.MessageUtils.textMessage(successMessage); - } catch (Exception e) { - return WonRdfUtils.MessageUtils.textMessage("Caught exception during validation: " + e); - } - } - }); - crawlConnectionDataBehaviour.activate(); - })); + }); + crawlConnectionDataBehaviour.activate(); + })); botCommands.add(new PatternMatcherTextMessageCommand("send dataset (agreements|claims|proposals)", - "download the connection data and returns dataset for (agreements|claims|proposals)", - Pattern.compile("^send dataset(\\s+((agreements)|(claims)|(proposals)))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "ok, I'll return the dataset - but I'll need to crawl the connection data first, please be patient.")); - // initiate crawl behaviour - CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(connection.getAtomURI(), - connection.getConnectionURI()); - CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, - command, Duration.ofSeconds(60)); - final StopWatch crawlStopWatch = new StopWatch(); - crawlStopWatch.start("crawl"); - crawlConnectionDataBehaviour - .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, crawlStopWatch)); - crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { - @Override - protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { - try { - logger.debug("validating data of connection {}", command.getConnectionURI()); - WonConnectionValidator validator = new WonConnectionValidator(); - StringBuilder message = new StringBuilder(); - boolean valid = validator.validate(successEvent.getCrawledData(), message); - String successMessage = "Connection " + command.getConnectionURI() + " is valid: " - + valid + " " + message.toString(); - if (matcher.matches()) { - Dataset dataSet = successEvent.getCrawledData(); - AgreementProtocolState agreementState = AgreementProtocolState.of(dataSet); - String dataSetString = new String(); - String filePrefix = new String(); - String param = matcher.group(2); - if ("agreements".equals(param)) { - // add data as file - // attachment to message - filePrefix = "agreementData"; - dataSetString = RdfUtils.toString(agreementState.getAgreements()); - } else if ("proposals".equals(param)) { - filePrefix = "proposalData"; - dataSetString = RdfUtils.toString(agreementState.getPendingProposals()); - } else if ("claims".equals(param)) { - filePrefix = "claimsData"; - dataSetString = RdfUtils.toString(agreementState.getClaims()); - } else { + "download the connection data and returns dataset for (agreements|claims|proposals)", + Pattern.compile("^send dataset(\\s+((agreements)|(claims)|(proposals)))?$", + Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "ok, I'll return the dataset - but I'll need to crawl the connection data first, please be patient.")); + // initiate crawl behaviour + CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent( + connection.getAtomURI(), + connection.getConnectionURI()); + CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour( + ctx, + command, Duration.ofSeconds(60)); + final StopWatch crawlStopWatch = new StopWatch(); + crawlStopWatch.start("crawl"); + crawlConnectionDataBehaviour + .onResult(new SendMessageReportingCrawlResultAction(ctx, connection, + crawlStopWatch)); + crawlConnectionDataBehaviour.onResult(new SendMessageOnCrawlResultAction(ctx, connection) { + @Override + protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEvent) { + try { + if (matcher.matches()) { + String successMessage = "Retrieved datased for connection: "; + Dataset dataSet = successEvent.getCrawledData(); + AgreementProtocolState agreementState = AgreementProtocolState.of(dataSet); + String dataSetString = new String(); + String filePrefix = new String(); + String param = matcher.group(2); + if ("agreements".equals(param)) { + // add data as file + // attachment to message + filePrefix = "agreementData"; + dataSetString = RdfUtils.toString(agreementState.getAgreements()); + } else if ("proposals".equals(param)) { + filePrefix = "proposalData"; + dataSetString = RdfUtils.toString(agreementState.getPendingProposals()); + } else if ("claims".equals(param)) { + filePrefix = "claimsData"; + dataSetString = RdfUtils.toString(agreementState.getClaims()); + } else { + throw new Exception("Second command param not known"); + } + if (dataSetString.length() < 1) { + return WonRdfUtils.MessageUtils.textMessage( + "No " + param + " data found for this conversation"); + } + Date date = new Date(); + String fileName = filePrefix + "-" + date.getTime() + ".trig"; + byte[] fileContent = dataSetString.getBytes("UTF-8"); + String encodedString = Base64.getEncoder().encodeToString(fileContent); + return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, + "application/trig", successMessage); + } + throw new Exception("Command param not known"); + } catch (Exception e) { return WonRdfUtils.MessageUtils - .textMessage("Caught exception during dataset retrieval"); + .textMessage("Caught exception during dataset retrieval: " + e); } - Date date = new Date(); - String fileName = filePrefix + "-" + date.getTime() + ".trig"; - byte[] fileContent = dataSetString.getBytes("UTF-8"); - String encodedString = Base64.getEncoder().encodeToString(fileContent); - return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, - "application/trig", successMessage); - } - - return WonRdfUtils.MessageUtils.textMessage(successMessage); - } catch (Exception e) { - return WonRdfUtils.MessageUtils.textMessage("Caught exception during validation: " + e); - } - } - }); - crawlConnectionDataBehaviour.activate(); - })); - botCommands.add(new PatternMatcherTextMessageCommand("retract (mine|proposal)", - "retract the last (proposal) message you sent, or the last message I sent", - Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(3) != null; - boolean retractProposes = matcher.group(4) != null; - String whose = useWrongSender ? "your" : "my"; - String which = retractProposes ? "proposal " : ""; - referToEarlierMessages(ctx, bus, connection, - "ok, I'll retract " + whose + " latest " + which - + "message - but 'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getNthLatestMessage(m -> retractProposes - ? (m.isProposesMessage() || m.isProposesToCancelMessage()) - && m.getEffects().stream().anyMatch(MessageEffect::isProposes) - : useWrongSender ? m.getSenderAtomURI().equals(connection.getTargetAtomURI()) - : m.getSenderAtomURI().equals(connection.getAtomURI()), - 0); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRetracts, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot retract any messages - I did not find any."; } - Optional retractedString = state.getTextMessage(uris[0]); - String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby retracting " + whose + " message" + finalRetractedString - + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " - + getDurationString(queryDuration) + " seconds."; }); - })); + crawlConnectionDataBehaviour.activate(); + })); + botCommands.add(new PatternMatcherTextMessageCommand("retract (mine|proposal)", + "retract the last (proposal) message you sent, or the last message I sent", + Pattern.compile("^retract(\\s+((mine)|(proposal)))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(3) != null; + boolean retractProposes = matcher.group(4) != null; + String whose = useWrongSender ? "your" : "my"; + String which = retractProposes ? "proposal " : ""; + referToEarlierMessages(ctx, bus, connection, + "ok, I'll retract " + whose + " latest " + which + + "message - but 'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getNthLatestMessage(m -> retractProposes + ? (m.isProposesMessage() + || m.isProposesToCancelMessage()) + && m.getEffects().stream().anyMatch( + MessageEffect::isProposes) + : useWrongSender ? m.getSenderAtomURI() + .equals(connection.getTargetAtomURI()) + : m.getSenderAtomURI().equals(connection + .getAtomURI()), + 0); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRetracts, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot retract any messages - I did not find any."; + } + Optional retractedString = state.getTextMessage(uris[0]); + String finalRetractedString = retractedString + .map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby retracting " + whose + " message" + + finalRetractedString + + " (uri: " + uris[0] + ")." + + "\n The query for finding that message took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("reject (yours)", - "reject the last rejectable message I (you) sent", - Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean useWrongSender = matcher.group(2) != null; - String whose = useWrongSender ? "my" : "your"; - referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose - + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( - useWrongSender ? connection.getAtomURI() : connection.getTargetAtomURI()); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addRejects, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot reject any of " + whose - + " messages - I did not find any suitable message."; - } - Optional retractedString = state.getTextMessage(uris[0]); - String finalRetractedString = retractedString.map(s -> ", which read, '" + s + "'") - .orElse(", which had no text message"); - return "Ok, I am hereby rejecting " + whose + " message" + finalRetractedString - + " (uri: " + uris[0] + ")." + "\n The query for finding that message took " - + getDurationString(queryDuration) + " seconds."; - }); - })); + "reject the last rejectable message I (you) sent", + Pattern.compile("^reject(\\s+(yours))?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean useWrongSender = matcher.group(2) != null; + String whose = useWrongSender ? "my" : "your"; + referToEarlierMessages(ctx, bus, connection, "ok, I'll reject " + whose + + " latest rejectable message - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestProposesOrClaimsMessageSentByAtom( + useWrongSender ? connection.getAtomURI() + : connection.getTargetAtomURI()); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addRejects, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot reject any of " + whose + + " messages - I did not find any suitable message."; + } + Optional retractedString = state.getTextMessage(uris[0]); + String finalRetractedString = retractedString + .map(s -> ", which read, '" + s + "'") + .orElse(", which had no text message"); + return "Ok, I am hereby rejecting " + whose + " message" + + finalRetractedString + + " (uri: " + uris[0] + ")." + + "\n The query for finding that message took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("propose (my|any) (N)", - "propose one (N, max 9) of my(/your/any) messages for an agreement", - Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> { - matcher.matches(); - boolean my = matcher.group(3) != null; - boolean any = matcher.group(4) != null; - int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); - boolean allowOwnClauses = any || !my; - boolean allowCounterpartClauses = any || my; - String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" - : allowCounterpartClauses ? "your" - : " - sorry, don't know which ones to choose, actually - "; - referToEarlierMessages(ctx, bus, connection, "ok, I'll make a proposal containing " + count + " of " - + whose - + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", - state -> state.getNLatestMessageUris(m -> { - URI ownedAtomUri = connection.getAtomURI(); - URI targetAtomUri = connection.getTargetAtomURI(); - return ownedAtomUri != null && ownedAtomUri.equals(m.getSenderAtomURI()) - && allowOwnClauses - || targetAtomUri != null && targetAtomUri.equals(m.getSenderAtomURI()) - && allowCounterpartClauses; - }, count + 1).subList(1, count + 1), WonRdfUtils.MessageUtils::addProposes, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot propose the messages - I did not find any."; - } - // Optional proposedString = - // state.getTextMessage(uris[0]); - return "Ok, I am hereby making the proposal, containing " + uris.length + " clauses." - + "\n The query for finding the clauses took " - + getDurationString(queryDuration) + " seconds."; - }); - })); + "propose one (N, max 9) of my(/your/any) messages for an agreement", + Pattern.compile("^propose(\\s+((my)|(any))?\\s*([1-9])?)?$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + matcher.matches(); + boolean my = matcher.group(3) != null; + boolean any = matcher.group(4) != null; + int count = matcher.group(5) == null ? 1 : Integer.parseInt(matcher.group(5)); + boolean allowOwnClauses = any || !my; + boolean allowCounterpartClauses = any || my; + String whose = allowOwnClauses ? allowCounterpartClauses ? "our" : "my" + : allowCounterpartClauses ? "your" + : " - sorry, don't know which ones to choose, actually - "; + referToEarlierMessages(ctx, bus, connection, "ok, I'll make a proposal containing " + count + + " of " + + whose + + " latest messages as clauses - but I'll need to crawl the connection data first, please be patient.", + state -> state.getNLatestMessageUris(m -> { + URI ownedAtomUri = connection.getAtomURI(); + URI targetAtomUri = connection.getTargetAtomURI(); + return ownedAtomUri != null && ownedAtomUri.equals(m.getSenderAtomURI()) + && allowOwnClauses + || targetAtomUri != null + && targetAtomUri.equals( + m.getSenderAtomURI()) + && allowCounterpartClauses; + }, count + 1).subList(1, count + 1), WonRdfUtils.MessageUtils::addProposes, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot propose the messages - I did not find any."; + } + // Optional proposedString = + // state.getTextMessage(uris[0]); + return "Ok, I am hereby making the proposal, containing " + uris.length + + " clauses." + + "\n The query for finding the clauses took " + + getDurationString(queryDuration) + " seconds."; + }); + })); botCommands.add(new PatternMatcherTextMessageCommand("accept", - "accept the last proposal/claim made (including cancellation proposals)", - Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestPendingProposalOrClaim(Optional.empty(), - Optional.of(connection.getTargetAtomURI())); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addAccepts, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null) { - return "Sorry, I cannot accept any proposal - I did not find pending proposals"; - } - return "Ok, I am hereby accepting your latest proposal (uri: " + uris[0] + ")." - + "\n The query for finding it took " + getDurationString(queryDuration) - + " seconds."; - }))); + "accept the last proposal/claim made (including cancellation proposals)", + Pattern.compile("^accept$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll accept your latest proposal - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestPendingProposalOrClaim(Optional.empty(), + Optional.of(connection.getTargetAtomURI())); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addAccepts, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null) { + return "Sorry, I cannot accept any proposal - I did not find pending proposals"; + } + return "Ok, I am hereby accepting your latest proposal (uri: " + uris[0] + + ")." + + "\n The query for finding it took " + + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("cancel", - "propose to cancel the newest agreement (that wasn't only a cancellation)", - Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), - (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, - "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", - state -> { - URI uri = state.getLatestAgreement(); - return uri == null ? Collections.EMPTY_LIST : Collections.singletonList(uri); - }, WonRdfUtils.MessageUtils::addProposesToCancel, - (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { - if (uris == null || uris.length == 0 || uris[0] == null || state == null) { - return "Sorry, I cannot propose to cancel any agreement - I did not find any"; - } - return "Ok, I am hereby proposing to cancel our latest agreement (uri: " + uris[0] + ")." - + "\n The query for finding it took " + getDurationString(queryDuration) - + " seconds."; - }))); + "propose to cancel the newest agreement (that wasn't only a cancellation)", + Pattern.compile("^cancel$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> referToEarlierMessages(ctx, bus, connection, + "ok, I'll propose to cancel our latest agreement - but I'll need to crawl the connection data first, please be patient.", + state -> { + URI uri = state.getLatestAgreement(); + return uri == null ? Collections.EMPTY_LIST + : Collections.singletonList(uri); + }, WonRdfUtils.MessageUtils::addProposesToCancel, + (Duration queryDuration, AgreementProtocolState state, URI... uris) -> { + if (uris == null || uris.length == 0 || uris[0] == null || state == null) { + return "Sorry, I cannot propose to cancel any agreement - I did not find any"; + } + return "Ok, I am hereby proposing to cancel our latest agreement (uri: " + + uris[0] + ")." + + "\n The query for finding it took " + + getDurationString(queryDuration) + + " seconds."; + }))); botCommands.add(new PatternMatcherTextMessageCommand("inject", - "send a message in this connection that will be forwarded to all other connections we have", - Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), (Connection connection, Matcher matcher) -> { - bus.publish(new ConnectionMessageCommandEvent(connection, - "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); - // build a message to be injected into all connections of the receiver atom - // (not - // controlled by us) - Model messageModel = WonRdfUtils.MessageUtils.textMessage("This is the injected message."); - // the atom whose connections we want to inject into - URI targetAtom = connection.getTargetAtomURI(); - // we iterate over our atoms and see which of them are connected to the - // remote - // atom - Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); - Set targetConnections = myatoms.stream() - // don't inject into the current connection - .filter(uri -> !connection.getAtomURI().equals(uri)).map(uri -> { - // for each of my (the bot's) atoms, check if they are - // connected to the remote - // atom of the current conversation - Dataset atomNetwork = WonLinkedDataUtils.getConnectionNetwork(uri, - ctx.getLinkedDataSource()); - return WonRdfUtils.AtomUtils.getTargetConnectionURIsForTargetAtoms(atomNetwork, - Collections.singletonList(targetAtom), Optional.of(ConnectionState.CONNECTED)); - }).flatMap(Collection::stream).collect(Collectors.toSet()); - bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, targetConnections)); - })); + "send a message in this connection that will be forwarded to all other connections we have", + Pattern.compile("^inject$", Pattern.CASE_INSENSITIVE), + (Connection connection, Matcher matcher) -> { + bus.publish(new ConnectionMessageCommandEvent(connection, + "Ok, I'll send you one message that will be injected into our other connections by your WoN node if the inject permission is granted")); + // build a message to be injected into all connections of the receiver atom + // (not + // controlled by us) + Model messageModel = WonRdfUtils.MessageUtils.textMessage("This is the injected message."); + // the atom whose connections we want to inject into + URI targetAtom = connection.getTargetAtomURI(); + // we iterate over our atoms and see which of them are connected to the + // remote + // atom + Set myatoms = ctx.getBotContextWrapper().retrieveAllAtomUris(); + Set targetConnections = myatoms.stream() + // don't inject into the current connection + .filter(uri -> !connection.getAtomURI().equals(uri)).map(uri -> { + // for each of my (the bot's) atoms, check if they are + // connected to the remote + // atom of the current conversation + Dataset atomNetwork = WonLinkedDataUtils.getConnectionNetwork(uri, + ctx.getLinkedDataSource()); + return WonRdfUtils.AtomUtils.getTargetConnectionURIsForTargetAtoms( + atomNetwork, + Collections.singletonList(targetAtom), + Optional.of(ConnectionState.CONNECTED)); + }).flatMap(Collection::stream).collect(Collectors.toSet()); + bus.publish(new ConnectionMessageCommandEvent(connection, messageModel, targetConnections)); + })); // activate ServiceAtomBehaviour serviceAtomBehaviour = new ServiceAtomBehaviour(ctx); serviceAtomBehaviour.activate(); // activate TextMessageCommandBehaviour textMessageCommandBehaviour = new TextMessageCommandBehaviour(ctx, - botCommands.toArray(new TextMessageCommand[0])); + botCommands.toArray(new TextMessageCommand[0])); textMessageCommandBehaviour.activate(); // eagerly cache RDF data BotBehaviour eagerlyCacheBehaviour = new EagerlyPopulateCacheBehaviour(ctx); @@ -534,53 +572,54 @@ protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEve // as soon as the echo atom triggered by debug connect created, connect to // original bus.subscribe(AtomCreatedEventForDebugConnect.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), welcomeMessage + " " + welcomeHelpMessage))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new ConnectWithAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), + welcomeMessage + " " + welcomeHelpMessage))); // as soon as the echo atom triggered by debug hint command created, hint to // original bus.subscribe(AtomCreatedEventForDebugHint.class, - new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, - new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), - SocketType.ChatSocket.getURI(), matcherUri))); + new RandomDelayedAction(ctx, CONNECT_DELAY_MILLIS, CONNECT_DELAY_MILLIS, 1, + new HintAssociatedAtomAction(ctx, SocketType.ChatSocket.getURI(), + SocketType.ChatSocket.getURI(), matcherUri))); // if the original atom wants to connect - always open bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), - new PublishSetChattinessEventAction(ctx, true)); + new OpenConnectionDebugAction(ctx, welcomeMessage, welcomeHelpMessage), + new PublishSetChattinessEventAction(ctx, true)); // if the remote side opens, send a greeting and set to chatty. bus.subscribe(ConnectFromOtherAtomEvent.class, noInternalServiceAtomEventFilter, - new PublishSetChattinessEventAction(ctx, true)); + new PublishSetChattinessEventAction(ctx, true)); // filter to prevent reacting to message Commands NotFilter noTextMessageCommandsFilter = getNoTextMessageCommandFilter(); bus.subscribe(ConnectFromOtherAtomEvent.class, - new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), - new DebugBotIncomingGenericMessageAction(ctx)); + new AndFilter(noTextMessageCommandsFilter, noInternalServiceAtomEventFilter), + new DebugBotIncomingGenericMessageAction(ctx)); // if the bot receives a text message - try to map the command of the text // message to a DebugEvent bus.subscribe(MessageFromOtherAtomEvent.class, noTextMessageCommandsFilter, - new DebugBotIncomingGenericMessageAction(ctx)); + new DebugBotIncomingGenericMessageAction(ctx)); bus.subscribe(CloseCommandSuccessEvent.class, new PublishSetChattinessEventAction(ctx, false)); // react to close event: set connection to not chatty bus.subscribe(CloseFromOtherAtomEvent.class, new PublishSetChattinessEventAction(ctx, false)); MessageTimingManager timingManager = new MessageTimingManager(ctx); // on every actEvent there is a chance we send a chatty message bus.subscribe(ActEvent.class, - new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, - DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, - DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); + new SendChattyMessageAction(ctx, CHATTY_MESSAGE_PROBABILITY, timingManager, + DebugBotIncomingGenericMessageAction.RANDOM_MESSAGES, + DebugBotIncomingGenericMessageAction.LAST_MESSAGES)); // process eliza messages with eliza bus.subscribe(MessageToElizaEvent.class, new AnswerWithElizaAction(ctx)); // remember when we sent the last message bus.subscribe(WonMessageSentOnConnectionEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); // remember when we got the last message bus.subscribe(WonMessageReceivedOnConnectionEvent.class, - new RecordMessageReceivedTimeAction(ctx, timingManager)); + new RecordMessageReceivedTimeAction(ctx, timingManager)); // initialize the sent timestamp when the connect message is received bus.subscribe(ConnectFromOtherAtomEvent.class, new RecordMessageSentTimeAction(ctx, timingManager)); // Usage Command Event Subscriptions: bus.subscribe(ReplaceDebugAtomContentCommandEvent.class, new ReplaceDebugAtomContentAction(ctx)); bus.subscribe(SendNDebugCommandEvent.class, new SendNDebugMessagesAction(ctx, DELAY_BETWEEN_N_MESSAGES, - DebugBotIncomingGenericMessageAction.N_MESSAGES)); + DebugBotIncomingGenericMessageAction.N_MESSAGES)); // react to the hint and connect commands by creating an atom (it will fire // correct atom created for connect/hint // events) @@ -591,7 +630,6 @@ protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEve bus.subscribe(SetChattinessDebugCommandEvent.class, new SetChattinessAction(ctx)); // react to a bot command activating/deactivating eager caching bus.subscribe(SetCacheEagernessCommandEvent.class, new BaseEventBotAction(ctx) { - @Override protected void doRun(Event event, EventListener executingListener) throws Exception { if (event instanceof SetCacheEagernessCommandEvent) { @@ -622,30 +660,32 @@ private interface TextMessageMaker { } private void referToEarlierMessages(EventListenerContext ctx, EventBus bus, Connection con, - String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, - TextMessageMaker textMessageMaker) { + String crawlAnnouncement, MessageFinder messageFinder, MessageReferrer messageReferrer, + TextMessageMaker textMessageMaker) { bus.publish(new ConnectionMessageCommandEvent(con, crawlAnnouncement)); // initiate crawl behaviour CrawlConnectionCommandEvent command = new CrawlConnectionCommandEvent(con.getAtomURI(), con.getConnectionURI()); CrawlConnectionDataBehaviour crawlConnectionDataBehaviour = new CrawlConnectionDataBehaviour(ctx, command, - Duration.ofSeconds(60)); + Duration.ofSeconds(60)); final StopWatch crawlStopWatch = new StopWatch(); crawlStopWatch.start("crawl"); AgreementProtocolState state = WonConversationUtils.getAgreementProtocolState(con.getConnectionURI(), - ctx.getLinkedDataSource()); + ctx.getLinkedDataSource()); crawlStopWatch.stop(); Duration crawlDuration = Duration.ofMillis(crawlStopWatch.getLastTaskTimeMillis()); getEventListenerContext().getEventBus() - .publish(new ConnectionMessageCommandEvent(con, - "Finished crawl in " + getDurationString(crawlDuration) + " seconds. The dataset has " - + state.getConversationDataset().asDatasetGraph().size() + " rdf graphs.")); + .publish(new ConnectionMessageCommandEvent(con, + "Finished crawl in " + getDurationString(crawlDuration) + + " seconds. The dataset has " + + state.getConversationDataset().asDatasetGraph().size() + + " rdf graphs.")); Model messageModel = makeReferringMessage(state, messageFinder, messageReferrer, textMessageMaker); getEventListenerContext().getEventBus().publish(new ConnectionMessageCommandEvent(con, messageModel)); crawlConnectionDataBehaviour.activate(); } private Model makeReferringMessage(AgreementProtocolState state, MessageFinder messageFinder, - MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { + MessageReferrer messageReferrer, TextMessageMaker textMessageMaker) { int origPrio = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); StopWatch queryStopWatch = new StopWatch(); @@ -656,7 +696,7 @@ private Model makeReferringMessage(AgreementProtocolState state, MessageFinder m Thread.currentThread().setPriority(origPrio); Duration queryDuration = Duration.ofMillis(queryStopWatch.getLastTaskTimeMillis()); Model messageModel = WonRdfUtils.MessageUtils - .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); + .textMessage(textMessageMaker.makeTextMessage(queryDuration, state, targetUriArray)); return messageReferrer.referToMessages(messageModel, targetUriArray); } From f380b55e45e0d84d35ab64084f6f9940837d34d1 Mon Sep 17 00:00:00 2001 From: maxstolze Date: Tue, 10 Mar 2020 12:00:07 +0100 Subject: [PATCH 6/6] Added TRIG formatting --- .../java/won/bot/debugbot/impl/DebugBot.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/won/bot/debugbot/impl/DebugBot.java b/src/main/java/won/bot/debugbot/impl/DebugBot.java index d4bae3e..be2b408 100644 --- a/src/main/java/won/bot/debugbot/impl/DebugBot.java +++ b/src/main/java/won/bot/debugbot/impl/DebugBot.java @@ -10,6 +10,7 @@ */ package won.bot.debugbot.impl; +import java.io.StringWriter; import java.lang.invoke.MethodHandles; import java.net.URI; import java.text.DecimalFormat; @@ -28,6 +29,7 @@ import org.apache.jena.query.Dataset; import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.RDFDataMgr; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StopWatch; @@ -98,10 +100,10 @@ import won.protocol.model.Connection; import won.protocol.model.ConnectionState; import won.protocol.model.SocketType; -import won.protocol.util.RdfUtils; import won.protocol.util.WonConversationUtils; import won.protocol.util.WonRdfUtils; import won.protocol.util.linkeddata.WonLinkedDataUtils; +import won.protocol.util.pretty.Lang_WON; import won.protocol.validation.WonConnectionValidator; /** @@ -274,7 +276,11 @@ protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEve if ("attach".equals(param)) { // add data as file // attachment to message - String dataSetInput = RdfUtils.toString(successEvent.getCrawledData()); + StringWriter writer = new StringWriter(); + Lang_WON.init(); + RDFDataMgr.write(writer, successEvent.getCrawledData(), + Lang_WON.TRIG_WON_CONVERSATION); + String dataSetInput = writer.toString(); Date date = new Date(); String fileName = "conversationData-" + date.getTime() + ".trig"; byte[] fileContent = dataSetInput.getBytes("UTF-8"); @@ -319,30 +325,34 @@ protected Model makeSuccessMessage(CrawlConnectionCommandSuccessEvent successEve String successMessage = "Retrieved datased for connection: "; Dataset dataSet = successEvent.getCrawledData(); AgreementProtocolState agreementState = AgreementProtocolState.of(dataSet); - String dataSetString = new String(); + String datasetString = new String(); String filePrefix = new String(); + StringWriter writer = new StringWriter(); + Lang_WON.init(); + Dataset dataset = null; String param = matcher.group(2); if ("agreements".equals(param)) { - // add data as file - // attachment to message filePrefix = "agreementData"; - dataSetString = RdfUtils.toString(agreementState.getAgreements()); + dataset = agreementState.getAgreements(); } else if ("proposals".equals(param)) { filePrefix = "proposalData"; - dataSetString = RdfUtils.toString(agreementState.getPendingProposals()); + dataset = agreementState.getPendingProposals(); } else if ("claims".equals(param)) { filePrefix = "claimsData"; - dataSetString = RdfUtils.toString(agreementState.getClaims()); + dataset = agreementState.getClaims(); } else { throw new Exception("Second command param not known"); } - if (dataSetString.length() < 1) { + if (dataset == null) { return WonRdfUtils.MessageUtils.textMessage( "No " + param + " data found for this conversation"); } + RDFDataMgr.write(writer, dataset, + Lang_WON.TRIG_WON_CONVERSATION); + datasetString = writer.toString(); Date date = new Date(); String fileName = filePrefix + "-" + date.getTime() + ".trig"; - byte[] fileContent = dataSetString.getBytes("UTF-8"); + byte[] fileContent = datasetString.getBytes("UTF-8"); String encodedString = Base64.getEncoder().encodeToString(fileContent); return WonRdfUtils.MessageUtils.fileMessage(encodedString, fileName, "application/trig", successMessage);