Adding DOTS tasklet command interaction

Among the many things that sets DOTS apart from the Domino Agent Manager is the opportunity to develop tasklets that can react to console commands. There might be many reasons for wanting that: it could be useful to refresh the currently cached tasklet configuration or running a specific task extemporaneously and even enable debugging for that run and that run only.

Giving a tasklet such capabilities is actually quite simple:

Tasklet commands definition

Let’s use the enum approach to store which commands we will make available for the tasklet.

public class WatchmailTask extends AbstractServerTask {

	protected enum Command {
		reload,
		program {
			@Override
			public String toHelpString() {
				return "program= [debug=]";
			}
		},
		debug {
			@Override
			public Object fromString(String s) {
				return Boolean.valueOf(s);
			}

			@Override
			public String toHelpString() {
				return null;
			}
		},
		help;

		public Object fromString(String s) {
			return s;
		}

		public String toHelpString() {
			return this.toString();
		}

		public static String printHelp() {
			StringBuffer buffer = new StringBuffer();

			buffer.append("---Domino OSGi Watchmail Tasklet Container Commands---\n");

			for (Command c : values()) {
				if (c.toHelpString() != null) {
					buffer.append(c.toHelpString() + "\n");
				}
			}

			return buffer.toString();
		}

		public static Map<Command, Object> fromArguments(String[] args) throws IllegalArgumentException {
			Map<Command, Object> m = new EnumMap<Command, Object>(Command.class);

			for (String arg : args) {
				Matcher matcher = ARGUMENT_PATTERN.matcher(arg);

				if (matcher.find()) {
					Command c = valueOf(matcher.group(1));

					m.put(c, matcher.groupCount() > 1 ? c.fromString(matcher.group(2)) : null);
				}
			}

			return m;
		}

		private static final Pattern ARGUMENT_PATTERN = Pattern.compile("-{0,2}(\\w*)=?(.*)");
	}
	
	...

Granted, the above code might look like lots of code lines. Yes, but such structured enum will make it for easy management later on.

To expound on that, we have 4 defined commands (or arguments): reload, program, debug (actually this is treated as additional parameter and not as a directly invokable command) and help.

Each enum comes with 2 ovverridable methods: fromString and toHelpString. fromString can evaluate the passed argument value to a more specialized class than the default String (eg. debug=true to Boolean). toHelpStringworks hand in hand with the static method Command.printHelp in order to provide some insight regarding which commands are valid and what mandatory and optional additional parameters they take if any. At the end of the article I will show some console examples.

Command.fromArguments is the static helper method that will process any given argument we will pass to the tasklet when invoking it from the console. Argument syntax is determined by Pattern ARGUMENT_PATTERN. In can take command or command=value. Prefix dashes are contemplated (eg. -command, --command).

Tasklet command invocation method

Once the commands are defined we just have to provide a tasklet dedicated method that we will use to process the commands.

public class WatchmailTask extends AbstractServerTask {

	...

	@Run(id = "invoke")
	public void invoke(String[] args, IProgressMonitor monitor) {
		Map<Command, Object> commands = null;

		try {
			commands = Command.fromArguments(args);
		} catch (IllegalArgumentException e) {
			logException(e);
			
			logMessage(Command.printHelp());

			return;
		}

		if (commands.containsKey(Command.reload)) { // RELOAD
			if (Preferences.INSTANCE.isRunning()) {
				logMessage("The task won't run because an instance of it is already running.");

				return;
			}

			runOnStart(monitor);
			
			logMessage("Configuration reloaded");
		} else if (commands.containsKey(Command.program)) { // PROGRAM
			if (Preferences.INSTANCE.isRunning()) {
				logMessage("The task won't run because an instance of it is already running.");

				return;
			}

			String programId = (String) commands.get(Command.program);

			if (programId == null) {
				logMessage(Command.printHelp());

				return;
			}

			boolean debug = false;

			if (commands.containsKey(Command.debug)) {
				debug = (Boolean) commands.get(Command.debug);
			}

			if (debug) {
				logMessage("[DEBUG] program ID " + programId);
			}

			// Your logic here

			logMessage("Tasklet execution completed.");
		} else { // HELP OR UNRECOGNIZED COMMAND
			logMessage(Command.printHelp());
		}
	}
	
	...

By means of the annotation @Run(id = "invoke") we make the invoke method identifiable and invokable by means of the word invoke.

So, what does the invocation syntax look like now?
Assuming our plugin task id is named WatchmailTask (see plugin.xml) we can type the following commands and expect the following outcomes:

tell dots run WatchmailTask.invoke help

[0CC8:0018-08F0] 06/27/2016 11:04:23 AM  [DOTS] (WatchmailTask.invoke) ---Domino OSGi Watchmail Tasklet Container Commands---
[0CC8:0018-08F0] 06/27/2016 11:04:23 AM  [DOTS] (WatchmailTask.invoke) reload
[0CC8:0018-08F0] 06/27/2016 11:04:23 AM  [DOTS] (WatchmailTask.invoke) program= [debug=]
[0CC8:0018-08F0] 06/27/2016 11:04:23 AM  [DOTS] (WatchmailTask.invoke) help
tell dots run WatchmailTask.invoke reload

[0CC8:0019-0EBC] 06/27/2016 11:40:42 AM  [DOTS] (WatchmailTask.invoke) Configuration reloaded
tell dots run WatchmailTask.invoke program=dummy

[0CC8:001A-08F0] 06/27/2016 11:41:21 AM  [DOTS] (WatchmailTask.invoke) Tasklet execution completed.
tell dots run WatchmailTask.invoke program=dummy debug=true

[0CC8:001B-08F0] 06/27/2016 11:41:53 AM  [DOTS] (WatchmailTask.invoke) Debugging for program ID dummy
[0CC8:001B-08F0] 06/27/2016 11:41:53 AM  [DOTS] (WatchmailTask.invoke) Tasklet execution completed.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.