-
Notifications
You must be signed in to change notification settings - Fork 36
Model And Plugin Design
The basic idea of the new BladeRunnerJS model is to provide a single encapsulated store of all BladeRunnerJS domain knowledge. This will be provided as a navigable, self-discoverable object model, in much the same vein as the Document Object Model present in all browsers. This is not only useful internally, allowing us to discard lots of duplicated code containing this domain knowledge and to get rid of lots of latent bugs due to the current lack of encapsulation, but will also be critical externally so that developers can create BladeRunnerJS plugins that perform useful work.
- The API must make any future refactoring of our directory structures easy to do.
- There should be very little cost required to create new model nodes, with most of the code being shared between all nodes.
- Exploratory model navigation must be easy for the reader to comprehend.
- We should use logical rather than physical navigation whenever possible.
- The reader should be able to differentiate physical navigation (which should be avoided) from logical navigation.
- Location and ancestor discovery for bad paths should fail slow when this is just going to be used to do downwards navigation.
- Upwards discovery can use the cache, whereas downwards discovery must refresh the cache.
- Exploratory model navigation can add to the cache if directory existence caching or automatic path creation are used.
These design goals were reverse engineered from this set of observed directory model access use cases:
- Root Access (null -> root model-node)
- Existent Ancestor Discovery (directory -> typed model-node)
- Existent Child Discovery (model-node -> child model-nodes)
- Logical Exploratory Model Navigation (model-node -> model-node)
- Physical Model Navigation (model-node -> model-node)
- Directory Access (model-node -> directory)
- Directory Existence Checking
- Automatic Path Creation
brjs.locate(fileWithinABlade, Blade.class).seedResources();
brjs.app("fxtrader").bladeset("fx").blade("ticket").dirExists();
brjs.app("fxtrader").bladeset("fx").blade("ticket").create();
brjs.app("fxtrader").bladeset("fx").blade("ticket").runTests();
The directory structure that the final model will need to be able to support is as follows:
. |-- apps | |-- a1 | | |-- a1-aspect | | | |-- resources | | | | |-- aliases.xml | | | | |-- html | | | | |-- i18n | | | | `-- xml | | | |-- src | | | |-- tests | | | | |-- test-type1 | | | | | |-- tech1 | | | | | | |-- resources | | | | | | | |-- aliases.xml | | | | | | | |-- html | | | | | | | |-- i18n | | | | | | | `-- xml | | | | | | |-- src-test | | | | | | `-- tests | | | | | `-- tech2 | | | | `-- test-type2 | | | |-- themes | | | | |-- t1 | | | | `-- t2 | | | `-- unbundled-resources | | | `-- empty.txt | | |-- a2-aspect | | |-- bladeset-bs1 | | | |-- blades | | | | |-- b1 | | | | | |-- resources | | | | | | |-- aliasDefinitions.xml | | | | | | |-- html | | | | | | |-- i18n | | | | | | `-- xml | | | | | |-- src | | | | | |-- tests | | | | | | |-- test-type1 | | | | | | | |-- tech1 | | | | | | | | |-- resources | | | | | | | | | |-- aliases.xml | | | | | | | | | |-- html | | | | | | | | | |-- i18n | | | | | | | | | `-- xml | | | | | | | | |-- src-test | | | | | | | | `-- tests | | | | | | | `-- tech2 | | | | | | `-- test-type2 | | | | | |-- themes | | | | | | |-- t1 | | | | | | `-- t2 | | | | | `-- workbench | | | | | |-- resources | | | | | | |-- aliases.xml | | | | | | |-- html | | | | | | |-- i18n | | | | | | |-- style | | | | | | `-- xml | | | | | |-- src | | | | | `-- test | | | | `-- b2 | | | |-- resources | | | | |-- aliasDefinitions.xml | | | | |-- html | | | | |-- i18n | | | | `-- xml | | | |-- src | | | | `-- empty.txt | | | |-- tests | | | | |-- test-type1 | | | | | |-- tech1 | | | | | | |-- resources | | | | | | | |-- aliases.xml | | | | | | | |-- html | | | | | | | |-- i18n | | | | | | | `-- xml | | | | | | |-- src-test | | | | | | `-- tests | | | | | `-- tech2 | | | | `-- test-type2 | | | `-- themes | | | |-- t1 | | | `-- t2 | | `-- bladeset-bs2 | `-- a2 |-- conf | |-- bladerunner.conf | |-- java | |-- test-runner.conf | `-- users.properties |-- integration-test-conf |-- js-patches |-- performance-test-conf |-- sdk | |-- bladerunner | |-- bladerunnerAscii.txt | |-- bladerunner.cmd | |-- docs | | |-- jsdoc | | | `-- empty.txt | | `-- release-notes | |-- libs | | |-- java | | | |-- application | | | |-- system | | | `-- testRunner | | `-- javascript | | |-- caplin | | | |-- resources | | | | |-- aliasDefinitions.xml | | | | |-- html | | | | |-- i18n | | | | `-- xml | | | `-- src | | `-- thirdparty | | |-- l1 | | | `-- library.manifest | | `-- l2 | |-- log | |-- loginRealm.conf | |-- system-applications | |-- templates | | |-- app-template | | |-- aspect-template | | |-- bladeset-template | | |-- blade-template | | `-- cutlass-template | `-- version.txt
A model that hopefully supports this directory structure, while abstracting away any relationship to the underlying directory structure wherever possible, is as follows:
These classes are used by the model, but are not directly part of it:
Plugins are discovered components that implement one of a number of pre-defined interfaces. The designs for these interfaces have been reached using the following new use cases:
- LessCss Support Including Sourcemaps (
BundlerPlugin
) - CommonJS Support (
FileTransformPlugin
) - EcmaScript 6 Support (
FileTransformPlugin
) - TypeScript Support Including Sourcemaps (
BundlerPlugin
) - CoffeeScript Support Including Sourcemaps (
FileTransformPlugin
orBundlerPlugin
) - Individually Served JavaScript Files In Development (
BundlerPlugin
) - Closure Compiler In Production Including Sourcemaps (
BundlerPlugin
) - UglifyJs2 In Production Including Sourcemaps (
BundlerPlugin
) - Ability To Include All Bundles Of A Given Type (
LogicalTagPlugin
) - Js-Test-Driver Support (
TestPlugin
) - WebDriver Support (
TestPlugin
) - Karma Support (
TestPlugin
) - Multi-Core Testing Support (
TestPlugin
)
Plus of course, it was heavily affected by the desire to retain all the advantages of the Caplin version of BladeRunner that we already had.
High level exception classes are used to allow the controller code to determine how error messages should be presented to the user and/or what should happen next:
The following utility classes are made available so that rich directory models (like the one above), with legacy support and backwards compatibility support can quickly be created and modified:
BRJS.handleLogicalRequest()
:
package com.caplin.brjs.model;
public class BRJS extends AbstractBRJSRootNode {
// snip...
private LogicalRequestHandler requestHandler = new LogicalRequestHandler();
// snip...
public void handleLogicalRequest(BladerunnerUri requestUri, java.io.OutputStream os) throws BundlerPluginException, IOException {
requestHandler.handle(requestUri, os);
}
// snip...
}
LogicalRequestHandler
:
package com.caplin.brjs.model.utility;
public class LogicalRequestHandler {
private Map bundlers = new HashMap<>();
public void handle(BladerunnerUri requestUri, OutputStream os) throws BundlerPluginException, IOException {
File baseDir = new File(BRJS.root.dir(), requestUri.scopePath);
BundlableNode bundlableNode = BRJS.root.locateFirstBundlableAncestorNode(baseDir);
BundlerPlugin bundler = bundlers.get(getResourceBundlerName(requestUri));
ParsedRequest request = (ParsedRequest) bundler.getRequestParser().parse(requestUri.logicalPath);
bundler.handleRequest(request, bundlableNode, os);
}
private String getResourceBundlerName(BladerunnerUri requestUri) {
return requestUri.logicalPath.substring(requestUri.logicalPath.lastIndexOf('.') + 1);
}
}
JsLogicalTagPlugin
:
package com.caplin.brjs.core.plugin.resourcebundler.js;
public class JsLogicalTagPlugin implements LogicalTagPlugin {
@Override
public String getTagName() {
return "js.bundle";
}
@Override
public void writeDevTag(Map tagAttributes, BundlableNode bundlableNode, String locale, Writer writer) throws IOException {
for (BundlerPlugin bundler : bundlableNode.parentApp().bundlerPlugins("text/javascript")) {
writeRequests(writer, bundler.getDevBundlerRequests(bundlableNode, locale));
}
}
@Override
public void writeProdTag(Map tagAttributes, BundlableNode bundlableNode, String locale, Writer writer) throws IOException {
for (BundlerPlugin bundler : bundlableNode.parentApp().bundlerPlugins("text/javascript")) {
writeRequests(writer, bundler.getProdBundlerRequests(bundlableNode, locale));
}
}
private void writeRequests(Writer writer, List requests) throws IOException {
for (String request : requests) {
writer.write("<script type='text/javascript' src='" + request + "'></script>\n");
}
}
}
JsBundlerPlugin
:
package com.caplin.brjs.core.plugin.bundler.js;
public class JsBundlerPlugin implements BundlerPlugin {
private final RequestParser requestParser;
{
RequestParserBuilder requestParserBuilder = new RequestParserBuilder();
requestParserBuilder.accepts("js/-bundle.js").as("bundle-request")
.and("js/src/.js").as("source-file-request");
requestParser = requestParserBuilder.build();
}
@Override
public String getMimeType() {
return "text/javascript";
}
@Override
public String getBundlerName() {
return "js";
}
@Override
public RequestParser getRequestParser() {
return requestParser;
}
@Override
public List getDevBundlerRequests(BundlableNode bundlableNode, String locale) {
List requests = new ArrayList<>();
for(SourceFile sourceFile : bundlableNode.getBundleSet().getSourceFiles()) {
requests.add(requestParser.createRequest("single-file", sourceFile.getClassName()));
}
return requests;
}
@Override
public List getProdBundlerRequests(BundlableNode bundlableNode, String locale) {
List requests = new ArrayList<>();
requests.add(requestParser.createRequest("bundle", getBundlerName()));
return requests;
}
@Override
public void handleRequest(ParsedRequest request, BundlableNode bundlableNode, OutputStream os) throws BundlerPluginException, IOException {
// TODO: ensure we use the correct character encoding
Writer writer = new OutputStreamWriter(os);
if(request.requestFormName.equals("source-file-request")) {
SourceFile sourceFile = bundlableNode.getClassFile(request.requestProperties.get("sourceFile"));
IOUtils.copy(sourceFile.getReader(), writer);
}
else if(request.requestFormName.equals("bundle-request")) {
for(SourceFile sourceFile : bundlableNode.getBundleSet().getSourceFiles()) {
writer.write("// " + sourceFile.getClassName() + "\n");
IOUtils.copy(sourceFile.getReader(), writer);
writer.write("\n\n");
}
}
}
}
- Commands:
DependencyAnalyserCommandPlugin
CheckCommandPlugin
CopyBladesetCommandPlugin
CreateApplicationCommandPlugin
CreateAspectCommandPlugin
CreateBladeCommandPlugin
CreateBladesetCommandPlugin
ImportApplicationCommandPlugin
BladerunnerCommandPlugin
TestCommandPlugin
TestServerCommandPlugin
ExportCommandPlugin
ExportMotifCommandPlugin
JsDocCommandPlugin
PackageDepsCommandPlugin
J2eeifyCommandPlugin
- Bundlers (all bundlers also implement
TagHandlerPlugin
andServletPlugin
):CompositeJsBundlerPlugin
NodeJsBundlerPlugin
CaplinJsBundlerPlugin
CompositeCssBundlerPlugin
CssBundlerPlugin
XmlBundlerPlugin
HtmlBundlerPlugin
I18nBundlerPlugin
ThirdPartyResourceBundlerPlugin
- Minifiers:
SimpleConcatPlugin
ClosurePlugin
- Tests:
JsTestDriverTestPlugin
WebDriverTestPlugin
- Commands:
ResetDbCommandPlugin
- File Transforms:
JNDIFileTransformPlugin
- Model Observers:
ResetDbModelObserverPlugin
Although we're not creating these now, we need to ensure our interfaces will be able to support these plugins so that we or other developers can create them at a later date:
- Commands:
-
ExportToHerokuCommandPlugin
(deploy apps to the cloud) -
ImportFromGitCommandPlugin
(import apps shared on git) -
CopyThemeCommandPlugin
(create a new theme based on an old one)
-
- Tag Handlers:
-
ImageTagPlugin
(allow images to be used outside of CSS pages)
-
- Bundlers:
-
JsonBundlerPlugin
(allow JSON config to be bundled) TypeScriptBundlerPlugin
CoffeeScriptBundlerPlugin
LessCssBundlerPlugin
-
- File Transforms:
-
JsHintFileTransformPlugin
(addconsole.warning()
lines for any JsHint feedback) -
TryInProductionFilterPlugin
(allowtry/catch
to be used in production, but not dev)
-
- Minifiers:
-
UglifyJsPlugin
(multi-level sourcemap support)
-