ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Introducing [fleXive] - A Complementary Approach to Java EE 5 Web Development

by Markus Plesser and Daniel Lichtenberger
05/01/2008

The daily bread and butter of an architect or developer dealing with web applications usually consists of a great many repetitive tasks. These start with setting up a development environment, choosing and downloading libraries (or let tools like Maven download them), creating basic build scripts, and wiring up all necessary components. After some time a naked skeleton for a web application is ready and waiting for further coding. While these steps are easy and can be efficiently handled by automation tools, other tasks like managing users, choosing a viable form of persistence (file based, JDBC, Hibernate, JPA, etc.), and implementing security for your sensitive data will still require a lot more time and effort.

There are many solutions out there that deal with some of these issues, but in most cases with some drawbacks: e.g., Ruby on Rails -- it is great and works well, but may not have corporate penetration, especially if a Java or .Net platform is already a company standard. We won't delve into the .Net world -- since this is a quite different situation than your typical Java environment -- but having a look at Java and especially Java EE, a web application will in most cases use JSF as its web framework, and the choice for a viable persistence framework will usually be Hibernate or JPA (in some Application Servers implemented using Hibernate). Depending on the use of some scaffolding tools you'll soon have some very basic versions of forms to create, read, edit, and delete data instances.

So far it has been pretty straightforward -- now imagine you also need authorization and authentication -- not only to be able to use (and hence see) data from your application, but even more to restrict access in a finer grained way than the usual "all or nothing" approach. You'll soon end up coding your own custom tailored mini-security framework - maybe based on established open source libraries like OSUser or Acegi coupled with some JAAS code.

Over the years, the authors did the same tasks over and over again. We learned a lot -- in particular about the capabilities and effort to integrate various libraries, as well as their major advantages and drawbacks -- and came up with a list of requirements for a framework:

[fleXive] core components
Figure 1. [fleXive] core components

At its heart [fleXive] is a pure Java EE 5 application, the core is made up of EJB3 beans, sharing common states and configuration using a clustered cache (including out-of-the-box support for JBoss Cache 2.x with pluggable interfaces that could be used for other providers like GigaSpaces or Coherence), while the web layer is based on JSF using Facelets, Richfaces/Ajax4JSF, and the Dojo toolkit. As a persistence alternative to JPA/Hibernate (which can be used as well of course) [fleXive] comes with its own persistence implementation offering some advantages like integrated ACL based security, versioning, support for multilingual data types, inheritance, and reuse. The persistence framework is not intended as an object-to-relational mapper, but rather as generic objects with all instance data accessible using XPath-like statements or traversing object graphs.

All these so called engines (implemented as Enterprise JavaBeans) can be used in your project. [fleXive] supports you by creating application skeletons where you just have to implement your business logic, use some of the pre-made JSF user interface components while giving you the freedom to use which ever Java EE 5 compatible library you wish.

[fleXive]-based Applications
Figure 2. [fleXive] support for writing applications

A big advantage of using [fleXive] is the powerful, and extendable, backend application where you can model your data structures, manage users and security, visually create queries, store search results in so called briefcases, or edit your data instances.

While being designed and written from scratch, [fleXive] uses very mature and approved concepts dating back to 1999. Originally intended as a framework for content management systems it grew to a feature reach multi purpose framework incorporating state of the art open source projects and tools.

Not everything is perfect yet and some features (like import/export and webservice support) are still in the works, but the majority of the framework is very stable and solid and soon ready for production use. Since we at UCS (unique computing solutions gmbh), the company sponsoring [fleXive] and being responsible for development, believe in OpenSource and "give and take," we decided to release the whole framework licensed under the LGPL v2.1 or higher.

A backend application showcasing most of [fleXive]'s features which is built on top of the framework is licensed under the GPL v2 or higher. It helps you to visually manage most aspects of [fleXive] - like defining data structures, building queries, manage users and security, etc.

And while we are currently the only ones maintaining and extending [fleXive] we certainly do hope for some positive feedback, feature requests, and helping hands when it comes to development and documentation from you, the community, to make [fleXive] a valuable choice for upcoming web applications.

We tried not to reinvent the wheel, but to make it easier and faster to develop web applications using up-to-date technology, provide means to extend the framework using plugins, and provide a backend administration application that is ready to use and can easily be adopted to your needs.

Current development snapshots and the "Release Candidate 1" are available for download at http://www.flexive.org/download - the final release following hopefully soon after [fleXive] is feature complete and more or less bug free. For further information please have a look at the roadmap.

Getting Started with [fleXive]

The prerequisites to get started with [fleXive] are minimal: you need a Java 5 SDK (Java 6 if you want to use JSR-223 scripting), Apache Ant 1.7 or higher, a MySQL 5.x database, and a Java EE 5 Application Server (currently JBoss 4.2.2 GA and Glassfish v2 are fully supported with some experimental support for Geronimo 2.1)

Detailed information about setting up MySQL and configuring your application server of choice can be found in the installation chapter of the reference documentation.

After downloading and unzipping the [fleXive] binary distribution -- we recommend using the current development build until the final release is available -- you will find the following directory layout:

.
|-- META-INF/
|-- build.xml
|-- database.properties
|-- applications/
|-- extlib/
|-- lib/
|-- templates/

The applications folder contains all [fleXive] applications that should be included in the enterprise archive (EAR) file. The standard distribution includes the backend administration application in this directory. To remove an application from the EAR, simply remove it from this directory and rebuild the EAR file.

Third-party library files required for compiling and running [fleXive]-based applications are stored in the extlib folder, while all [fleXive] libraries are contained in the lib folder.

The next step is to adapt the database.properties file to your needs (host, port, username, and password) and create the database schema by executing the following line at the command prompt:

ant db.create db.config.create

You will be prompted to enter a name for your division database (simply hit enter to keep flexive).

To create a new project, you need to call the ant target project.create. Doing so will create a project folder (the name of the project will be asked when invoking the ant task) in the same directory where the [fleXive] distribution is stored, i.e., the current parent directory. For the example in the next section we created a project named products.

To learn more about available Ant targets and creating [fleXive] application skeletons, please have a look at the getting started section of the reference documentation.

Let's Write Code

To demonstrate some of [fleXive]'s core features, we create a small application that manages products and their manufacturers. The data input will be done in the generic backend application, the custom front-end application will be written in JSF.

Start off by creating a new application with

ant project.create

Use "products" for the name. Change to the newly created directory:

cd ../products

The directory structure of the application is as follows:

    |-- lib
    |-- resources
    |   |-- META-INF
    |   |-- messages
    |   |-- scripts
    |   |   |-- library
    |   |   |-- runonce
    |   |   `-- startup
    |   `-- templates
    |-- src
    |   `-- java
    |       |-- ejb
    |       |-- shared
    |       `-- war
    `-- web
        `-- index.xhtml

The [fleXive] build system automatically recognizes resources and code dropped in these directories: for example, if you add classes to src/java/ejb, an EJB jar will be generated and added to your application. If you place localized messages in resources/messages, these will be available through [fleXive] message beans, and so on.

Now we're set for development, but first we have to think about the data model:

There are several ways to create our types in [fleXive]:

For the sake of simplicity, we choose Groovy to create our data types. The first argument to the GroovyTypeBuilder is the type name. The body includes all properties to be created for the type. The following listing shows the creation of the manufacturer data model, including test data generation:

import ...
// get countries select list
final FxSelectList countries =
    CacheAdmin.environment.getSelectList(FxSelectList.COUNTRIES)

// create product manufacturer type
new GroovyTypeBuilder().manufacturer(description:
    new FxString("Manufacturer")) {
    name        (assignment: "ROOT/CAPTION",
                 description: new FxString(FxLanguage.ENGLISH,
                                "Name"))
    description (dataType: FxDataType.HTML,
                 multilang: true,
                 description: new FxString(FxLanguage.ENGLISH,
                                "Description"))
    logo        (dataType: FxDataType.Binary,
                 description: new FxString(FxLanguage.ENGLISH,
                                "Company Logo"))
    basedIn     (dataType: FxDataType.SelectOne,
                 referencedList: countries,
                 description: new FxString(FxLanguage.ENGLISH,
                                "Based in"))
}

// create test data
GroovyContentBuilder builder =
    new GroovyContentBuilder("manufacturer")
builder {
    name("Police Inc.")
    basedIn(new FxSelectOne(false,
        countries.getItemByData("ca")))
    description(new FxHTML(FxLanguage.ENGLISH,
        "A global distributor of police-related accessoires.")
                .setTranslation(FxLanguage.GERMAN,
        "Ein weltweiter Distributor von Polizei-Accessoires."))
}
EJBLookup.contentEngine.save(builder.content)

builder = new GroovyContentBuilder("manufacturer")
builder {
    name("Amor")
    basedIn(new FxSelectOne(false,
        countries.getItemByData("it")))
    description(new FxHTML(FxLanguage.ENGLISH,
        "Love is Amor's business.")
                .setTranslation(FxLanguage.GERMAN,
        "Liebe ist Amor's Gesch\u00E4ft."))
}
EJBLookup.contentEngine.save(builder.content)

builder = new GroovyContentBuilder("manufacturer")
builder {
    name("Atlantic Enterprises")
    basedIn(new FxSelectOne(false,
        countries.getItemByData("us")))
    description(new FxHTML(FxLanguage.ENGLISH,
        "Atlantic deals with poker hardware.")
                .setTranslation(FxLanguage.GERMAN,
   "Atlantic ist ein Hersteller von Gl\u00FCcksspiel-Hardware."))
}
EJBLookup.contentEngine.save(builder.content)

The script code can be executed in [fleXive]'s scripting console, located in the backend application at Administration / Scripts / Groovy console. You can see the resulting data structure in the backend's Structure tab.

Since we can hardly rely on the users of our application executing this code snippet for every installation, we place the script file in the resources/scripts/runonce folder. This way, it will be run exactly once for each database this application runs on (there is also a startup folder for scripts that are executed on every application startup).

The backend application offers a powerful generic editor for arbitrary [fleXive] content types. Depending on the actual data structure, the editor offers buttons to create or remove input fields, as well as repositioning them (which is useful for "ordered" data structures, e.g., a news article consisting of multiple paragraphs and images).

To create new content -- as a first instance of the newly created type -- click on the Content Tree's tab on the left, right-click on the tree's root node, and select "Create content..." to get a list of all registered types. A manufacturer form, generated from the type definition above, looks like this:

manufacturer
Figure 3. Creating a new manufacturer content instance

The product editor offers a field to reference a manufacturer, as well as the nested variant group. You can add more variants by clicking on the image on the upper left corner of the box.

product
Figure 4. Creating a new product content instance

You can go on and create some product descriptions and manufacturers. Notice how new instances pop up in the "Content Tree" on the left side. You can even create your own hierarchy, for example by creating folders for product types. You can also opt to not use the "Content Tree" at all, it's just a helpful way of organizing data objects. To search for existing contents of a type using the context menu in the "Structure" tab on the left side.

Writing a Frontend - Meet the [fleXive] API

While the generic input forms are handy for managing your data without having to manually code editors, you also want a web page to show your data to the customer. [fleXive] provides a component toolkit based on JSF that makes it almost trivial to render [fleXive] contents in web applications. In this first tutorial, we'll implement a basic, read-only view of all our products that can easily be customized and extended in all the ways JSF has to offer.

Content Queries and Result Sets

The main page of our frontend displays a list of all products. To retrieve all products, we issue a query using [fleXive]'s custom search engine and render the result using a plain JSF datatable. Queries can be issued in a SQL-like dialect or using a Java builder class. We fetch the results in a JSF managed bean, which can then be accessed via JSF EL (i.e., #{productBean.products}). Since this class is part of the web application, we place it in the src/java/war folder. The package structure is up to you, we chose com.flexive.example.war.

public class ProductBean {
  private DataModel products;

  public DataModel getProducts() throws FxApplicationException {
    if (products == null) {
      final FxResultSet result =
               new SqlQueryBuilder()
               .select("@pk", "product/name", "product/price",
                       "product/variant/articlenumber")
               .filterVersion(VersionFilter.LIVE)
               .type("product")
               .orderBy("product/name", SortDirection.ASCENDING)
               .getResult();
      products = new FxResultSetDataModel(result);
    }
    return products;
  }
}

You could also issue the above query using the FxSQL query language, using the search engine EJB:

EJBLookup.getSearchEngine().search(
    "SELECT co.@pk, co.product/name, co.product/price, "+
    " co.product/variant/articlenumber " +
    "FROM content co " +
    "FILTER co.version=LIVE " +
    "WHERE (co.typedef = 7) " +
    "ORDER BY co.product/name",
    0, 1000, null)

Next we need a frontend page to render our products. [fleXive] uses Facelets for building JSF pages, which allows us to use a very clean XHTML syntax. All frontend pages are stored in the web directory, while component templates go to resources/templates. Since we'll need the product table on more than one page, we define a template in resources/templates/productTable.xhtml We also have to register the template in resources/META-INF/products.taglib.xml. The actual template is plain JSF: the datamodel returns a linear list of all found products, which are rendered using a JSF datatable.

<h:dataTable var="row" value="#{productBean.products}"
                     styleClass="products">
  <h:column>
    <fx:thumbnail pk="#{row[0]}"/>
  </h:column>
  <h:column>
    <f:facet name="header">
      #{fxMessageBean['products.productTable.column.product']}
    </f:facet>
    <fx:resultValue value="#{row[1]}"/>
  </h:column>
  <h:column>
    <f:facet name="header">
      #{fxMessageBean['products.productTable.column.price']}
    </f:facet>
      <fx:resultValue value="#{row[2]}"/> EUR
  </h:column>
  <h:column>
    <h:commandLink action="productDetails">
      <f:setPropertyActionListener value="#{row[0]}"
                                  target="#{productBean.pk}"/>
        #{fxMessageBean['products.productTable.button.details']}
      </h:commandLink>
  </h:column>
</h:dataTable>

The resulting HTML output looks like this:

product listing
Figure 5. Generated product list

Access content instances in JSF

In a product detail page (web/productDetails.xhtml), we show all fields of the selected product instance (stored in #{productBean.pk}) using the <fx:content> tag and provide a JSF link to the manufacturer's page. The content instance values can be accessed directly through a JSF-EL variable. For example, #{product.name} returns the name property of the currently shown product:

<fx:content var="product" pk="#{productBean.pk}">
    <div class="product">
        <h1>
            #{product.name}
        </h1>
    [...]

The product's manufacturer is a referential property, i.e., it references another content instance. To access the manufacturer's values, we dereference the manufacturer using a $ suffix, i.e. #{product.manufacturer$.name}. We also create a command link to open the manufacturer's page. #{product.manufacturer.defaultTranslation} returns the default (and in this case, only) translation of the manufacturer reference, a primary key object:

<h:commandLink action="manufacturerDetails">
  #{product.manufacturer$.name}
  <f:setPropertyActionListener
      value="#{product.manufacturer.defaultTranslation}"
      target="#{manufacturerBean.pk}"/>
</h:commandLink>

product details
Figure 6. Product detail page (with variants)

Rendering the images stored with a customer is also a simple task. The product's image property stores references to the binary (not the binary itself, since it would waste way too much memory), which can be rendered as an image using the <fx:thumbnail> tag. The $list suffix provides an iterator over all values of a property or group with a multiplicity greater than 1:

<ui:repeat var="image" value="#{product.image$list}">
  <fx:thumbnail binary="#"/>
</ui:repeat>

The final feature of the product page is the handling of the product's variant group. For each product variant (i.e., the base product with a color association), we render the article number, the color and a link to update the current article number (in the full products page, the variant's images will be added to the common product images):

<ui:repeat var="variant" value="#{product.variant$list}">
  <li>
    <h:commandLink>#{variant.color}
      <f:setPropertyActionListener
              value="#{variant.articleNumber}"
              target="#{productBean.articleNumber}"/>
    </h:commandLink>
    (#{variant.articleNumber})
  </li>
</ui:repeat>

manufacturer details
Figure 7. Manufacturer detail page

Run It!

If you have already installed your application server and defined the data sources as described in the installation guide, the project can be compiled and deployed. To do so, execute

ant

in the products directory. If all works out as planned, you will have a file called products.ear in the dist subdirectory - ready to be installed on your application server.

Content Versions, Workflows, and the Live Version

[fleXive] contents are versioned: when a content is edited, the user can choose to create a new version instead of modifying the existing one. Workflows provide a way of describing organizational structures for the users of an application: for example, a newspaper article may go through the workflow steps editing, review, and reviewed. Different users have different responsibilities, for example the article author may not change his own article from review to reviewed, while a reviewer cannot change an article while it's in the editing workflow step.

The live version indicates the version of a content that the actual application user may see. This might be the latest version, but could also be an archived version due to recent editing of the content. To make a content version the live version, you have to set it to the special workflow step Live. Only one version of any content instance can use this workflow step, so there is at most one live version of any given content.

You can see this concept in action in the product application: edit an existing product in the backend, change the workflow step from live to edit, and it will disappear from the front-end application. Or create a new version in the edit step, make some changes, and the front-end data will still be served from the live version until you set the new version to live.

Outlook

This article showcased some of [fleXive]'s major features: complex data structures, multi-linguality, versioning, and workflows. There are many more features left unexplained - for example the highly optimized content tree, providing a generic, hierarchical storage for tree nodes, the fine-grained security system down to individual properties based on ACL's (access control lists) and usergroups, or the scripting and document management facilities (like EXIF extraction from images and full-text indexing of PDF files). For more information on those features and much more, stay tuned for more articles and visit http://www.flexive.org, especially the extensive reference documentation.

Resources

Markus Plesser CTO and software architect at Vienna/Austria-based software developer UCS - unique computing solutions gmbh. He is one of the lead developers of [fleXive] and amongst other things responsible for the persistence engine. While interested in web technologies he prefers the server side of applications - namely EJB, Spring, etc.

Daniel Lichtenberger is a software engineer at Vienna/Austria-based software developer UCS - unique computing solutions gmbh. He develops enterprise software for the Java EE platform and has a keen interest in web technologies and agile development.


Return to ONJava.

Copyright © 2009 O'Reilly Media, Inc.