Custom workflow
Purpose
By default, WUIC uses transparently a default instance of each detected engine. Automatically, your nuts will be aggregated, inspected, compressed, etc, but this could be changed!
This tutorial shows how you can take the complete control over the engines WUIC uses when processing nuts.
Configuration
Imagine you want that your nuts are only compressed by YUCompressor
and cached by EhCache
. One possibility is to configure other engines to disable it but you can even remove them from the engines chain!
Let’s see the following configuration that defines nuts heaps imported with Thymeleaf
.
<heap id="css" dao-builder-id="myDao">
<nut-path>css/foo.css</nut-path>
</heap>
<heap id="js" dao-builder-id="myDao">
<nut-path>js/foo.js</nut-path>
</heap>
<wuic:html-import workflowId="css" />
<wuic:html-import workflowId="js" />
At this stage, you could think that the possible imports are based on the existing heaps. That’s wrong! Actually, when you import a set of nuts, this is done through a workflow. A workflow processes some heaps with particular engines. Why we did not declare them before? Because WUIC creates a workflow for each heap by default. Its ID is the heap ID so that’s why you could import some nuts with the ID of your heap.
Actually, to create a workflow, you need to refer to a workflow template which describes all the engines to be used. When a workflow is created automatically for a heap, WUIC use a default template with some default engines:
-
A cache based on a memory map for all type of nuts
-
A text aggregator for scripts
-
An image aggregator and an image compressor for images
-
etc
So if we want to specify specific engines in a workflow, we have to declare it:
<workflow-templates>
<workflow-template id="tpl">
<without>
<engine-builder-id>wuicDefaultMemoryMapCacheEngineBuilder</engine-builder-id>
</without>
<engine-chain>
<engine-builder-id>wuicDefaultEhCacheEngineBuilder</engine-builder-id>
<engine-builder-id>wuicDefaultYuiCompressorJavascriptEngineBuilder</engine-builder-id>
<engine-builder-id>wuicDefaultYuiCompressorCssEngineBuilder</engine-builder-id>
</engine-chain>
</workflow-template>
</workflow-templates>
<workflows>
<workflow id-prefix="my-" workflow-template-id="tpl" heap-id-pattern=".*" />
</workflows>
-
Note that we have excluded the cache based on a memory map because we only want to use
EhCache
to cache nuts -
As it is done for DAOs, WUIC creates default engine builders so you can refer there ID.
-
id-prefix
is a string that prefix the workflow ID which will be followed by the heap ID. -
heap-id-pattern
is a regular expression which defines the heaps to be processed by the engines specified in this workflow. -
By default, compression is enabled on YUI Compressor engines
-
By default, cache is enabled in EhCache engine which looks for a cache named
wuicCache
in aehcache.xml
file at the root of the classpath. If the cache is not found, it creates a default one.
With this configuration, WUIC will pick up appropriate engines for the nuts to be processed. For instance, YUICompressor
CSS minifier won’t be applied on a heap composed of Javascript nuts. Moreover, they will be chained according to their type. For instance, caching engine is already executed first in the chain of responsibility.
-
Note that if a heap is referenced in a custom workflow, then its default one won’t be created.
-
Also note that default engine will be injected if they are not in conflict with a specified one. In this case, cache based on memory map won’t be injected because we already specify
EhCache
support.
Finally, you will import your heap through your custom workflow like that:
<wuic:html-import workflowId="my-css" />
<wuic:html-import workflowId="my-js" />
Extend WUIC
Why extend?
You may extend WUIC for several purposes:
Resolve and process nuts differently
Both DAO and Engines can be configured in a XML configuration file. You can also do it with Java Config.
To do it, WUIC provides its own annotation processor architecture which detects all classes:
-
implementing
NutDao
, annotated @NutDaoService and declared in packagecom.github.wuic.dao
-
extending
Engine
, annotated @EngineService and declared in packagecom.github.wuic.engine
So you can create a new DAO like this:
package com.github.wuic.dao;
@NutDaoService
public class MyNutDao extends AbstractNutDao {
...
}
and an engine like this:
package com.github.wuic.engine;
@EngineService(injectDefaultToWorkflow = true)
public class MyEngine extends NodeEngine {
...
}
In this last example you can see the injectDefaultToWorkflow
annotation attribute that indicates that this engine will be in any workflow by default or not
Note that the code above uses base class that help you to implement NutDao
interface and extend Engine
class. You will find a lot of helpers in the javadoc:
-
Abstract classes for engine with different purpose (caching, aggregating, compressing, etc)
-
AbstractNutDao for DAO creation
Each engine and DAO needs to be configured differently so your class may expose a constructor expecting several parameters. You need in that case to rely on com.github.wuic.config package which contains:
-
@ConfigConstructor
annotation for you constructor -
Annotations for each parameter regarding their type:
String
,Integer
,Boolean
or more globalObject
For instance, an engine compressing nuts will looks like this:
@EngineService(injectDefaultToWorkflow = true)
public class MyCompressEngine extends NodeEngine {
@ConfigConstructor
public MyCompressEngine(
@BooleanConfigParam(propertyKey = "c.g.wuic.engine.compress", defaultValue = true)
Boolean compress) {
}
@Override
public List<NutType> getNutTypes() {
return Arrays.asList(NutType.values());
}
@Override
public EngineType getEngineType() {
return EngineType.CACHE;
}
@Override
protected List<ConvertibleNut> internalParse(EngineRequest request) throws WuicException {
return request.getNuts();
}
@Override
public Boolean works() {
return true;
}
}
ApplicationConfig already contains a lot of configuration keys that you can reuse. This is required to let WUIC build the object for you regarding the properties provided in XML
or Java Config
. With the previous sample, you can now do something like that in XML:
<wuic>
<engine-builders>
<engine-builder type="MyCompressEngineBuilder">
<property key="c.g.wuic.engine.compress">false</property>
</engine-builder>
</engine-builders>
</wuic>
WUIC generates a builder identified by [EngineName]Builder
and by default injects a default instance identified by wuicDefault[BuilderName]
into the workflow. The previous configuration simply changes the default value (true
to false
).
You will find several extensions already provided here.
Add support for templating project
WUIC already provides support for JSP
and Thymeleaf
users (see tutorials).
Both support address the same issues:
-
How to generate 1 HTTP request to load an aggregate result vs N HTTP requests when aggregation is disabled
-
How to manage configurations directly injected into the template and cache it to improve performances
Generate script import
To generate script import, you need to loop over the list returned by the workflow process:
final List<ConvertibleNut> nuts = facade.runWorkflow(workflowId, ProcessContext.DEFAULT);
for (final ConvertibleNut nut : nuts) {
final String i = HtmlUtil.writeScriptImport(nut, IOUtils.mergePath(facade.getContextPath(), workflowId));
}
Regarding your configuration, the process for a particular workflow will return a different result. For instance, the list will contain only one element per script type if aggregation is enabled, many otherwise.
Allow configuration from templating
You can also use the facade to specify additional configurations through templating support. You can rely on ContextBuilderConfigurator to configure the facade. For instance, imagine a Reader
points to the XML declared inside your template, then you can inject it like this:
facade.configure(new ReaderXmlContextBuilderConfigurator(reader,
toString(),
facade.allowsMultipleConfigInTagSupport(),
ProcessContext.DEFAULT));
Note: wuicFacade.allowsMultipleConfigInTagSupport()
returns the setting that allows to configure only once (production mode) or multiple times (development mode).