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 a ehcache.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:

  • Create a new way to access nuts, this is done with a DAO

  • Create a new nut processor, this is done with an engine

  • Create a support for a templating project to write HTML statements that load processed nuts

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 package com.github.wuic.dao

  • extending Engine, annotated @EngineService and declared in package com.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:

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 global Object

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).

You will find full samples in JSP and Thymeleaf support.