Friday, April 1, 2011

Grokking Seam Forge: Part 3

JBoss have just released Seam 3.0.0.Final, which includes Alpha 3 of Seam Forge. So I thought I'd improve my previous blog entry:

  • First, download Seam from here
  • Unzip to a folder of your choosing
  • Run bin\forge
Type the following commands and press ENTER after each one. Accept any defaults you are prompted for, and remember to use TAB and UP ARROW autocomplete:

new-project --named MyApp --topLevelPackage com.myapp
persistence setup --provider HIBERNATE --container JBOSS_AS6
new-entity --named Person
new-field string --named firstname
new-field string --named surname
new-field int --named age
new-field string --named notes
new-field custom --named homeAddress
[type=The qualified Class to be used as this field's type (of type java.lang.String)]: com.myapp.Address
new-field custom --named workAddress
[type=The qualified Class to be used as this field's type (of type java.lang.String)]: com.myapp.Address
new-entity --named Address
new-field string --named street
new-field string --named suburb
new-field string --named state
new-field string --named postcode
scaffold from-entity com.myapp.domain.Person.java
exit


Next:

  • Open Eclipse (with m2eclipse installed)

  • Choose File > Import > Existing Maven Projects and import the pom.xml at \seam-3.0.0.Final\forge\MyApp\pom.xml

  • Right click the pom.xml and choose Run As > Maven package

  • Take the WAR it generates under target/MyApp-1.0.0-SNAPSHOT.war and deploy it under JBoss AS 6.0.0.Final
Open your browser at http://localhost:8080/MyApp-1.0.0-SNAPSHOT/scaffold/person/list.jsf:
Now, let's play around a bit! These are things that soon Forge will be able to do for you, but for now edit Person.java and add the lines in bold:

package com.myapp.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Version;

import org.metawidget.inspector.annotation.UiComesAfter;
import org.metawidget.inspector.annotation.UiSection;

@Entity
public class Person
   implements java.io.Serializable {

   @Id
   private @GeneratedValue( strategy = GenerationType.AUTO )
   @Column( name = "id", updatable = false, nullable = false )
   long   id      = 0;

   @Version
   private @Column( name = "version" )
   int      version   = 0;

   public long getId() {

      return this.id;
   }

   public void setId( final long id ) {

      this.id = id;
   }

   public int getVersion() {

      return this.version;
   }

   public void setVersion( final int version ) {

      this.version = version;
   }

   @Column
   private String   firstname;

   public String getFirstname() {

      return this.firstname;
   }

   public void setFirstname( final String firstname ) {

      this.firstname = firstname;
   }

   @Column
   private String   surname;

   @UiComesAfter( "firstname" )
   public String getSurname() {

      return this.surname;
   }

   public void setSurname( final String surname ) {

      this.surname = surname;
   }

   @Column
   private int   age;

   @UiComesAfter( "surname" )
   public int getAge() {

      return this.age;
   }

   public void setAge( final int age ) {

      this.age = age;
   }

   @Column
   private String   notes;

   @UiComesAfter( "workAddress" )
   @Lob
   @UiSection( "Other" )

   public String getNotes() {

      return this.notes;
   }

   public void setNotes( final String notes ) {

      this.notes = notes;
   }

   @Column
   private Address   homeAddress = new Address();

   @UiComesAfter( "age" )
   @UiSection( "Details" )

   public Address getHomeAddress() {

      return this.homeAddress;
   }

   public void setHomeAddress( final Address homeAddress ) {

      this.homeAddress = homeAddress;
   }

   @Column
   private Address   workAddress = new Address();

   @UiComesAfter( "homeAddress" )
   public Address getWorkAddress() {

      return this.workAddress;
   }

   public void setWorkAddress( final Address workAddress ) {

      this.workAddress = workAddress;
   }

   @Override
   public String toString() {

      return this.getClass().getSimpleName() + "[" + id + ", " + version + ", " + firstname + ", " + surname + ", " + age + ", " + notes + ", " + homeAddress + ", " + workAddress + "]";
   }
}

Similarly for Address.java:

package com.myapp.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

import org.metawidget.inspector.annotation.UiComesAfter;

@Entity
public class Address
   implements java.io.Serializable {

   @Id
   private @GeneratedValue( strategy = GenerationType.AUTO )
   @Column( name = "id", updatable = false, nullable = false )
   long   id      = 0;

   @Version
   private @Column( name = "version" )
   int      version   = 0;

   public long getId() {

      return this.id;
   }

   public void setId( final long id ) {

      this.id = id;
   }

   public int getVersion() {

      return this.version;
   }

   public void setVersion( final int version ) {

      this.version = version;
   }

   @Column
   private String   street;

   public String getStreet() {

      return this.street;
   }

   public void setStreet( final String street ) {

      this.street = street;
   }

   @Column
   private String   suburb;

   @UiComesAfter( "street" )
   public String getSuburb() {

      return this.suburb;
   }

   public void setSuburb( final String suburb ) {

      this.suburb = suburb;
   }

   @Column
   private String   state;

   @UiComesAfter( "suburb" )
   public String getState() {

      return this.state;
   }

   public void setState( final String state ) {

      this.state = state;
   }

   @Column
   private String   postcode;

   @UiComesAfter( "state" )
   public String getPostcode() {

      return this.postcode;
   }

   public void setPostcode( final String postcode ) {

      this.postcode = postcode;
   }
}

Then tell metawidget.xml we'd like to use RichFaces:

<htmlMetawidget xmlns="java:org.metawidget.faces.component.html">
   ...
   <widgetBuilder>   
      <compositeWidgetBuilder xmlns="java:org.metawidget.widgetbuilder.composite" config="CompositeWidgetBuilderConfig">
         <widgetBuilders>
            <array>
               <overriddenWidgetBuilder xmlns="java:org.metawidget.faces.component.widgetbuilder"/>
               <readOnlyWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/>
               <richFacesWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder.richfaces"/>
               <htmlWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder" config="HtmlWidgetBuilderConfig"/>
            </array>
         </widgetBuilders>
      </compositeWidgetBuilder>
   </widgetBuilder>

   <layout>
      <tabPanelLayoutDecorator xmlns="java:org.metawidget.faces.component.html.layout.richfaces" config="TabPanelLayoutDecoratorConfig">
         <layout>
            <simpleLayout xmlns="java:org.metawidget.faces.component.layout"/>
         </layout>
      </tabPanelLayoutDecorator>
   </layout>


</htmlMetawidget>

And tell web.xml the same (update: this can be done using the richfaces-forge-plugin, see the comments below):

<web-app >
   ...
   <filter>
      <display-name>RichFaces Filter</display-name>
      <filter-name>richfaces</filter-name>
      <filter-class>org.ajax4jsf.Filter</filter-class>
   </filter>

   <filter-mapping>
      <filter-name>richfaces</filter-name>
      <servlet-name>Faces Servlet</servlet-name>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>FORWARD</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
   </filter-mapping>

   <servlet>
      <servlet-name>Faces Servlet</servlet-name>
      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>

   <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>*.jsf</url-pattern>
   </servlet-mapping>

</web-app>

And finally pom.xml too (update: this can be done using add-dependency, see the comments below):

<dependencies>
   ...
   <dependency>
      <groupId>org.richfaces.ui</groupId>
      <artifactId>richfaces-ui</artifactId>
      <version>3.3.3.Final</version>
      <scope>compile</scope>
   </dependency>
   <dependency>
      <groupId>org.richfaces.framework</groupId>
      <artifactId>richfaces-impl</artifactId>
      <version>3.3.3.Final</version>
      <scope>compile</scope>
   </dependency>

</dependencies>

Then redeploy your app and hit http://localhost:8080/MyApp-1.0.0-SNAPSHOT/scaffold/person/list.jsf again:
Of course, Seam Forge is still in alpha so not everything works yet. But hopefully this has whetted your appetite for things to come!

6 comments:

Lincoln Baxter, III said...

Richard, another awesome post!

I might add that you can put richfaces in the pom using forge as well:

$ project add-depenency org.richfaces.ui:richfaces-ui:3.3.3.Final

$ project add-depenency org.richfaces.framework::3.3.3.Final

Unknown said...

Nice Article!

Another way to add RichFaces to your forge app is using my RichFaces plugin for Forge:

forge git-plugin git://github.com/bleathem/richfaces-forge-plugin.git

and the run:
richfaces setup

Richard said...

Thanks guys. Blog post now updated. Wow, Forge is really moving fast!

Regards,

Richard.

M Chisty said...

This is not exactly a nice article. Those who work in J2EE/Seam/JSF projects, they will not appreciate some mechanical/auto-generated code. Forge forces you to use Metawidget; Metawidget generates mechanical code; for very simple Hello-World type projects, metawidget is fine, but not for Real life projects. Forge should have the flexibility to integrate/use Richfaces, seam directly .. etc, and not by going through Metawidget.

Richard said...

Christy,

To be fair, we've spent a lot of time making sure Metawidget *is* fine for Real Life projects. And lots of Real Life projects use it in production. If there is an area where Metawidget doesn't work for you, please explain it on the Metawidget forums. I'd love to improve it for you.

Having said that, Forge is not tied to Metawidget. We are looking to make Forge output static JSF tags in the near future.

Regards,

Richard.

Richard said...

FYI: outputting static JSF tags in JBoss Forge has now been released:

http://blog.kennardconsulting.com/2011/12/you-cant-spell-forge-without-metawidget.html