Introduction

In the first part of this two part blog, you were introduced to Fpak (Forge Package), the templating feature of Forge.  I covered how to download/compile/install Fpak, described the layout and structure of the .fpk file, and covered the use of the Runner to allow execution of a .fpk file outside of Forge.  In this second part, I attempt to convert the jboss-javaee6-webapps Maven Archetype to a .fpk file.  In this process, I will cover:

 

  • Generating a .fpk file with an existing project layout using the default Fpak generator
  • Wrapping a .fpk file with a plugin to allow it to be registered in Forge
  • Installing the wrapped .fpk file into Forge
  • Running the new plugin from Forge

 

Once these topics are covered, I will list some ideas for the improvement of the Fpak functionality.  There's a lot to cover, so let's get started.

 

Generating a .fpk file

Having a templating feature available to Forge begs the question, "How can I create the needed template?".  Well, the wizard developers would perhaps prefer to code them from scratch using vi (or emacs to prevent *that* holy war), but for the rest of us mere mortals we may want something that makes it easier for us.  So, Fpak has the ability to inspect an existing project layout and convert it to the necessary format for the file creation section of the .fpk file.  This would allow a developer to work with the project to get it right before codifying it in the .fpk file.  At this point there are a couple of caveats to the default generator provided by Fpak that should be noted.  First, Fpak was created as a templating engine to augment Forge and as such, works solely on text based files.  This is important because when you are trying to generate a .fpk file from an existing project, the binary resources of the project will *not* be accommodated.  One work around could be to extract binary files into a "binary resource" zip that can be overlaid onto a generated project.  Second, once a project is laid out and ready to be templatized, the tokens to be replaced will need to be manually modified in the .fpk file (e.g. class names or packaging names).

 

Generating from jboss-javaee6-webapps

Now that you've been introduced to the default generator, let's take a look at how it works in action.  For this, I will first run the jboss-javaee6-webapps archetype to generate a project layout that will become input into the default generator.  Here are the steps I used in accomplishing this:

 

  • create a directory to hold the generated project in
  • cd to the directory
  • execute the  mvn archetype command below using the values shown

 

$ mvn archetype:generate -DarchetypeArtifactId=jboss-javaee6-webapp -DarchetypeGroupId=org.jboss.weld.archetypes -DarchetypeRepository=central
Define value for property 'groupId': : GROUPIDREPLACE
Define value for property 'artifactId': : ARTIFACTIDREPLACE
Define value for property 'version':  1.0-SNAPSHOT: : 
Define value for property 'package':  GROUPIDREPLACE: : org.replace.pkg
[INFO] Using property: name = Java EE 6 webapp project
Confirm properties configuration:
groupId: GROUPIDREPLACE
artifactId: ARTIFACTIDREPLACE
version: 1.0-SNAPSHOT
package: org.replace.pkg
name: Java EE 6 webapp project
 Y: : 

 

I've used values that will be easy to replace in the .fpk file with the appropriate variable (token) syntax.  You can compile the generated project to ensure that it is valid and will build, but if you do, be sure to remove the target directory before running the generator on the project layout.  Now the default generator can be run from the project root directory.  The generator is in the jar generated in part 1 of the blog.  It can be run by executing the following command:

 

$ java -cp <fpak_directory>/target/fpak-1.0-SNAPSHOT.jar org.jboss.fpak.generator.FPakGenerator <generated_file_name>.fpk

 

This will generate the .fpk file in the project root directory by default.  I ran the default generator on the jboss-javaee6-webapp project generated in the previous step with a filename of g6-webapp.fpk (g6 inspired by a tweet from Dan Allen .  Remember that the generator only builds the file creation part of the Fpak file.  Now that you have that, there are a few more things to add:

 

  • @inputs section for grabbing any necessary input
  • @init section for any execution needed before processing the templates in the file creation section
  • replace the unique names generated by the archetype with the appropriate variables from either the @inputs or @init section
  • prepend the variable representing the base directory to all filenames

 

Here's what the @inputs and @init sections look like after being added:

 

@inputs:{
    String project : "Name of the project",
    String package : "Fully qualified package name",
    boolean help : "Prints help"
}

@init:{
    if (help) {
        System.out.println("--project <projectName> --package <packageName>");
    }

    if (project == null) {
        fail("You must specify a project name");
        return;
    }


    if (package == null) {
        fail("You must specify a package name");
        return;
    }


    var projectName = project;
    var projectClassName = projectName.substring(0, 1).toUpperCase() + projectName.substring(1);
    var packageName = package;
    var baseDirectory = projectName.toLowerCase();
    var sourceDirectory = packageName.replaceAll('\\\\.', '/');
}

 

Now that the needed variables are defined, GROUPIDREPLACE, ARTIFACTIDREPLACE, and org.replace.pkg can be replaced with @{packageName}, @{projectClassName}, and @{packageName} respectively.  The last step is prepending @{baseDirectory}/ to all the filenames in the file generation section (e.g. ++pom.xml: becomes ++@{baseDirectory}/pom.xml:).  The g6-webapp.fpk file is now complete and ready to be tested with the Runner.  Recall from part 1 of this blog that the Runner provides the ability to process a .fpk file without needed to bootstrap Forge.  The project can be (re)generated with Runner and then built and run to ensure it works.

 

Creating, loading, and running the Forge plugin wrapper

Forge currently doesn't recognize a .fpk file as a valid plugin type which theoretically it doesn't need to.  The gen command is built into Forge and is the mechanism Forge has for processing a .fpk file.  But, the gen command needs a way to discover the available .fpk files to be processed.  This is where the wrapper plugin comes into play.  The wrapper plugin basically allows the .fpk file to become available via the classloader and advertise the .fpk file to the gen command by a name.  If this seems a bit hard to understand, perhaps this piece of code can help illustrate:

 

@ApplicationScoped
public class G6ScaffoldPlugin implements Plugin {
    @Inject
    private Event<AdvertiseGenProfile> event;

    public void init(@Observes PostStartup postStartup) {
        URL g6_fpk = getClass().getClassLoader()
                .getResource("org/jboss/forge/templates/g6-webapp.fpk");

        event.fire(new AdvertiseGenProfile("g6-webapp", g6_fpk));
    }
}

 

Here you can see that the path to the g6-webapp.fpk file is captured as a URL.  That URL in turn is registered with the gen command with the key of g6-webapp via an AdvertiseGenProfile event being fired.  Now all that is needed is a jar file that contains both this wrapper class and the .fpk file.  An example project and pom.xml file can be found at [1].  Once you have a jar file, the jar file can be loaded into Forge using the forge jar-plugin command.  Here's an example:

 

[no project] bin $ forge jar-plugin --jar ~/work/forge-g6-webapp/target/forge-g6-webapp-1.0-SNAPSHOT.jar
 ? [id=plugin identifier, [e.g. "com.example.group : example-plugin"] (of type org.jboss.forge.project.dependencies.Dependency)]: id=g6-webapp
***INFO*** WARNING!
 ? Installing plugins from remote sources is dangerous, and can leave untracked plugins. Continue? [Y/n] 
Wrote /Users/rruss/.forge/plugins/id=g6-webapp$null$1$.jar
***SUCCESS*** Installed from [forge-g6-webapp-1.0-SNAPSHOT.jar] successfully.

 

Now g6-webapp is available to the gen command and can be run from Forge.  Here is an example of that:

 

[no project] work $ gen g6-webapp --help
--project <projectName> --package <packageName>
You must specify a project name
[no project] work $ gen g6-webapp --project blog-g6 --package org.blog.g6
created: /Users/rruss/work/blog-g6/pom.xml
created: /Users/rruss/work/blog-g6/readme.html
created: /Users/rruss/work/blog-g6/readme.txt
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/controller/MemberRegistration.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/data/MemberListProducer.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/data/MemberRepository.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/data/MemberRepositoryProducer.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/data/SeedDataImporter.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/model/Member.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/rest/JaxRsActivator.java
created: /Users/rruss/work/blog-g6/src/main/java/org/blog/g6/rest/MemberResourceRESTService.java
created: /Users/rruss/work/blog-g6/src/main/resources/import.sql
created: /Users/rruss/work/blog-g6/src/main/resources/META-INF/persistence.xml
created: /Users/rruss/work/blog-g6/src/main/resources-jbossas/default-ds.xml
created: /Users/rruss/work/blog-g6/src/main/webapp/index.jsf
created: /Users/rruss/work/blog-g6/src/main/webapp/index.xhtml
created: /Users/rruss/work/blog-g6/src/main/webapp/META-INF/context.xml
created: /Users/rruss/work/blog-g6/src/main/webapp/META-INF/MANIFEST.MF
created: /Users/rruss/work/blog-g6/src/main/webapp/resources/css/screen.css
created: /Users/rruss/work/blog-g6/src/main/webapp/resources/gfx/banner.png
created: /Users/rruss/work/blog-g6/src/main/webapp/resources/gfx/weld.png
created: /Users/rruss/work/blog-g6/src/main/webapp/WEB-INF/beans.xml
created: /Users/rruss/work/blog-g6/src/main/webapp/WEB-INF/faces-config.xml
created: /Users/rruss/work/blog-g6/src/main/webapp/WEB-INF/templates/default.xhtml
created: /Users/rruss/work/blog-g6/src/main/webapp/WEB-INF/web.xml
created: /Users/rruss/work/blog-g6/src/test/java/org/blog/g6/test/MavenArtifactResolver.java
created: /Users/rruss/work/blog-g6/src/test/java/org/blog/g6/test/MemberRegistrationTest.java
created: /Users/rruss/work/blog-g6/src/test/resources/arquillian.xml
created: /Users/rruss/work/blog-g6/src/test/resources-glassfish-embedded/sun-resources.xml
created: /Users/rruss/work/blog-g6/src/test/resources-glassfish-embedded/test-persistence.xml
created: /Users/rruss/work/blog-g6/src/test/resources-jbossas/jndi.properties
created: /Users/rruss/work/blog-g6/src/test/resources-jbossas/test-persistence.xml

 

The project can now be built just as if you had generated it from the original Maven Archetype.  Well ... almost.  If you recall from above, there was a very specific limitation listed.  Do you remember what that was?  Fpak was primarily built to only process text based files.  For anyone who has experience with the jboss-javaee6-webapp archetype, there are image (non-text) files in the project.  Those, and any other non text file, could not be accommodated by the default generator.  After you build the project generated from this plugin you will notice that when the webapp is deployed, you will see broken images.  "But wait, Rodney" you say to yourself, I see some .png files listed above as having been created.  Yup, the default generator tried to capture those files in the .fpk file, and the gen command in Forge tried to process them.  Unfortunately, that transition from being "converted" from binary to text back to binary did not quite preserve the files.

 

Wrap up

Before we take a look at what has been accomplished, I would like to state one more caveat.  While there can be some benefits to converting a Maven Archetype to an Fpak template, that wasn't necessarily what was being demonstrated here.  I used the conversion context to demonstrate the ability to generate a .fpk file from an existing project layout.  Alright, with that out of the way, let's take a look at what was demonstrated here:

 

  • You were introduced to the features and limitations of using the default generator from Fpak to help create a .fpk file from an existing project layout.  The existing project used here was a project that was generated from the jboss-javaee6-webapp Maven Archetype.
  • Once the .fpk file was completed, you were shown how to create a wrapper plugin to help register the created .fpk file with Forge.
  • The wrapper plugin class and the .fpk file were put into a jar file and loaded into Forge.  Once available, the gen command could process the .fpk file from within Forge.  An example of this was shown

 

Between both parts 1 and 2, I hope you were given enough information to understand how template support in Forge is accomplished via Fpak.

 

Opportunity for enhancement

I don't want to completely wrap up without acknowledging some opportunities for enhancements to Fpak's functionality.  Many of these ideas were borne out of writing this blog and identifying some gaps.  Here they are in no particular order of importance:

 

  • support for the conditional addition of a file with a suggested format of ?(conditional_expression)++filename:
  • auto-registration of a .fpk file with Forge to eliminate the need for a wrapper plugin
  • an intermediate step may be the auto-creation of a wrapper plugin, given a .fpk file
  • wire @inputs with Forge's CLI processing to provide <TAB> completion and heuristic argument discovery via <TAB>
  • make tokenizing values in a file easier from the generator
  • use Forge to actually generate the .fpk file and provide the opportunity to use commands to generate the @inputs and @init section
  • find a cleaner way to deal with binary resources of a project
  • .fpakignore file as input to the generator, similar in functionality to .gitignore

 

These are just a few of my ideas for improving Fpak.  What are you ideas for making this better/easier to use?

 

[1]  https://github.com/rdruss/forge-g6-webapp