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.
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 Command
s in the extension, and WebReporter
does likewise for NetLogo Reporter
s. 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.
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.
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)
).
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.
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.
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)
).
Provides the ability to adapt RequestMethod
s 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.
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.
A cornily-named object that provides some utility methods for simplifying common verifications:
-
ensuringExtensionContext
- Throws an exception if the current
Context
isn't anExtensionContext
. Should virtually never happen.
- Throws an exception if the current
-
ensuringGUIWorkspace
- Throws an exception if the workspace it is given isn't a
GUIWorkspace
.
- Throws an exception if the workspace it is given isn't a
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
.
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.
Provides an apply
method that will write out the contents of an InputStream
to a file.
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.