Knowing Your Way Around the Source - NetLogo/Web-Extension GitHub Wiki

Within the extension, several constructs exist for the sake of making it easier to create primitives and perform common operations in creating them. This section will walk you through those constructs—what they are, how they can be used, and how they can help you.

/src/prim

WebPrimitive

This trait provides many conveniences for making it easier to write NetLogo web primitives. If you add any primitives to this extension, they should probably extend WebPrimitive in some way. CommonWebPrimitive exists to simplify the parsing of most primitives—the ones of the syntactical format <prim name> <URL> <method> <param list>. SimpleWebPrimitive exists to streamline the parsing of the "simple" primitives (i.e. ImportWorld). What that all ultimately boils down to is that you can take the args: Array[Argument] that is passed into perform/report and run it through the inherited processArguments method to get back a correctly-typed, pattern-matchable List of the arguments without having to muddy up the code of perform/report—or even the primitive object's immediate code—with the parsing details.

Furthermore, WebCommand streamlines the implementation of NetLogo Commands in the extension, and WebReporter does likewise for NetLogo Reporters. More specifically, WebCommand and WebReporter allow smooth use of EnsuranceAgent within the bodies of their respective perform/report methods by providing a version of perform/report that takes the Context implicitly (as required by EnsuranceAgent.ensuringExtensionContext). When implementing perform or report, you should pretty much always override the version with the implicit Context and DummyImplicit, and never override the version with just one parameter list (the one like this: (args: Array[Argument], context: Context)).

Additionally, the WebPrimitive base implementation contains a few handy primitives for ensuring that streams get closed after operations. processResponse is a method that follows a similar principle, but taking a (response, statusCode) tuple as an argument (as is returned by Requester.apply). Building on top of even that, responseToLogoList will take a (response, statusCode) tuple, extract the string version of response, and return the string and statusCode together in standard LogoList list form. If your stream is processed within EventEvaluator/the event thread, you should not make use of these primitives, as they will close the stream before the event completes. Instead, have your asynchronously-running hook close the stream.

/src/requester

Requester

Requester is a trait that primarily exists to seamlessly solve the "exporting problem" of needing to hook into NetLogo, extract some information from a workspace, and then send it off through the HTTP library. It does this through the enforcement of the protected def generateAddedExportData: Option[String] method. It also has a basic implementation for coalescing HTTP parameters from multiple different locations into a single bundle of parameters to be included in the request, as well as adding the data and cookies (if any) to the request, and tapping into the HTTP library to make the request. If present, will read a cookie value from the netlogo.web.cookie Java system property and a destination URL from the netlogo.export_destination system property. System properties, however, have lower priority than passed-in arguments, so, if you both pass the Request.apply method a destination URL and set the netlogo.export_destination system property, the passed-in method argument will be used and the system property will be ignored.

RequesterGenerator

This trait enforces the existence of a protected def generateRequester: (RequesterCons) => Requester with Integration method, which is used to create a Requester that will take in a hook, read NetLogo data through the hook, and export the data through the Requester's access to the HTTP library (as explained above in the entry for Requester). If you need to make an HTTP request through the library, but you don't need to use a hook, a SimpleRequesterGenerator trait exists to remove the need to implement generateRequester and RequesterCons (which then defaults to (Unit)).

StreamerExporter

This Requester subclass reads hooks and outputs them as Option[ByteArrayInputStream]s. It expects to be mixed in with an OutStream subclass (i.e. GZIPStream or Base64Stream), so the output can change the output format from the hook. ByteStream can be used if you have no desire to morph the output.

WebIntegration

Provides an interface for defining extra properties that one might need to integration with some form of web interface. There are two existing implementations of WebIntegration:

  • SimpleWebIntegration
    • Very simple, basic implementation. Pretty much nothing there.
  • WISEIntegration
    • For uploading data to WISE.

Of course, feel free to extend WebIntegration to integrate with another API.

/src/requester/http

RequestMethod

Models HTTP request methods as Scala objects. RequestMethod has an apply method that takes a string and returns an Option of the corresponding RequestMethod subclass object for that name, if any (i.e. RequestMethod("post") returns Some(Post)).

RequestMethodConverter

Provides the ability to adapt RequestMethods to the expected entity formats of different HTTP libraries. Currently, the only HTTP library supported is the Apache Commons HTTP library, which is adapted for through the ToApacheConverter object. EntityParams and URLParams are also provided as common implementations of how to encode the transfer of parameters with the Apache Commons HTTP library.

RequestSender

Provides an interface for sending HTTP requests. Virtually all HTTP requests sent in this extension should go through this object. Current implementation leaves it tightly coupled to the Apache Commons HTTP library.

/src/util

EnsuranceAgent

A cornily-named object that provides some utility methods for simplifying common verifications:

  • ensuringExtensionContext
    • Throws an exception if the current Context isn't an ExtensionContext. Should virtually never happen.
  • ensuringGUIWorkspace
    • Throws an exception if the workspace it is given isn't a GUIWorkspace.

Example:

object ImportDrawing extends WebCommand with SimpleWebPrimitive {
  override def perform(args: Array[Argument])(implicit context: Context, ignore: DummyImplicit) {
    ensuringExtensionContext { (extContext: ExtensionContext) =>
      ensuringGUIWorkspace(extContext.workspace) { (guiWS: GUIWorkspace) =>
        val (dest) = processArguments(args)
        val is = new URL(dest).openStream()
        guiWS.importDrawing(is)
      }
    }
  }
}

Examples like this are quite common throughout the code for the primitives. In this example, when going to do perform, the implicit context is checked to ensure that it is an ExtensionContext, and, if it is, the code continues, assuming that an ExtensionContext known as extContext is in scope. Similarly, it then checks to ensure that extContext.workspace is a GUIWorkspace, and, if it is, continues execution of the method body, assuming that there is a GUIWorkspace, guiWS, in scope. If everything checks out, the drawing is then imported into guiWS.

EventEvaluator

Manages the execution of jobs onto the event thread. Generally speaking, EventEvaluator.apply should get the job done right for you, but, if it doesn't, EventEvaluator.withinWorkspace likely will.

FileWriter

Provides an apply method that will write out the contents of an InputStream to a file.

ImageToBase64

A typeclass that provides conversions to ImageToBase64, which is an interface with a single method: def asBase64 : String. Currently only provides an instance of ImageToBase64 for BufferedImage, but other instances could easily be added.

⚠️ **GitHub.com Fallback** ⚠️