Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: restore
Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "new-users" and space = currentSpace()

Principle 1 – Static Structure, Dynamic Behavior

The concept of "Dynamic Behavior" should be pretty obvious when you are building a web application; things should look different for different users/situations. But what does it mean that Tapestry has "Static Structure?" Static structure implies that when you build a page in Tapestry you are going to define all of the types of components that are used within that page. Under no circumstance during the rendering or event processing of the page will you be able to dynamically create a new type of component and place that into the component tree.

At first glance, this seems quite limiting ... other frameworks allow new elements to be created on the fly; it's also a common feature of desktop GUIs such as Swing. But static structure turns out to be not so limiting after all. You can create new elements (you're actually re-rendering existing components with different properties). And you have plenty of options for getting dynamic behavior out of your static structure; from the simple conditional and looping components to the more advanced implementations of Tapestry's BeanEditor or Grid components, Tapestry gives you control over what renders and when, and even where it appears on the page. And starting in Tapestry 5.3 you can even use the Dynamic component, which renders whatever is in an external template file.

Why did Tapestry choose static structure as a core principle? It's really a matter of meeting the requirements of agility and scalability.

Agility

Tapestry is designed to be an agile working environment; "code less, deliver more". To support you writing less code Tapestry does a lot of work on your POJO pages and components when first loading them. It also uses shared instances of page and component classes (shared across multiple threads and requests). Having dynamically modifiable structure would imply that each request has its own instance and, further, that the entire structure would need to be serialized between requests so that it can be restored to handle later requests.

Tapestry also makes you more agile by speeding up the development cycle with Live Class Reloading. Tapestry monitors the file system for changes to Java page classes, component classes, service implementation classes, HTML templates and component property files, and it hot-swaps the changes into the running application without requiring a restart or losing session data. This provides a very short code-save-view cycle that no other framework can touch.

Scalability

When building large scale systems it is important to consider how your resources are going to be used on each deployed server, and how that information is going to be shared between servers. Static structure means that page instances do not need to be stored inside the HttpSession and simple browsing users do not require extra system resources. This lean use of the HttpSession is key to Tapestry's very high scalability, especially in a clustered configuration. Again, linking an instance of a page to a particular client would require vastly more server-side resources than having a single shared page instance.

Principle 2 – Adaptive API

A key feature of Tapestry 5 is its adaptive API.

In traditional Java frameworks (including Struts, JSF and even the now-ancient Tapestry 4) user code is expected to conform to the framework. You create classes that extend from framework-provided base classes, or implement framework-provided interfaces.

This works well until you upgrade to the next release of the framework: with the new features of the upgrade, you will more often than not experience breaks in backwards compatibility. Interfaces or base classes will have changed and your existing code will need to be changed to match.

In Tapestry 5, the framework adapts to your code. You have control over the names of the methods, the parameters they take, and the value that is returned. This is driven by annotations, which tell Tapestry under what circumstances your methods are to be invoked.

For example, you may have a login form and have a method that gets invoked when the form is submitted:

Code Block
langjava
public class Login
{
  @Persist
  @Property
  private String userId;

  @Property
  private String password;

  @Component
  private Form form;

  @Inject
  private LoginAuthenticator authenticator;

  void onValidateFromForm()
  {
    if (! authenticator.isValidLogin(userId, password))
    {
      form.recordError("Invalid user name or password.");
    }
  }

  Object onSuccessFromForm()
  {
    return PostLogin.class;
  }
}

This short snippet demonstrates a bit about how Tapestry operates. Pages and services within the application are injected with the @Inject annotation. The method names, onValidateFromForm() and onSuccessFromForm(), inform Tapestry about when each method is to be invoked. This naming convention identifies the event that is handled, ("validate" and "success") and the id of the component from which the event is triggered (the "form" component).

The "validate" event is triggered to perform cross-field validations, and the "success" event is only triggered when there are no validation errors. The onSuccessFromForm() method's return value directs Tapestry on what to do next: jump to another page within the application (here identified as the class for the page, but many other options exist). When there are exceptions, the page will be redisplayed to the user.

By contrast, in Tapestry 4 the Form component's listener parameter would be bound to the method to invoke, by name. Further, the listener method had to be public. The Tapestry 5 approach not only supports multiple listeners, but also provides an improved separation of view concerns (inside the page's HTML template) and logic concerns, inside the Java class.

In many cases, additional information about the event is available and can be passed into the method simply by adding parameters to the method. Again, Tapestry will adapt to your parameters, in whatever order you supply them.

Tapestry also saves you needless effort: the @Property annotation marks a field as readable and writable; Tapestry will provide the accessor methods automatically.

Finally, Tapestry 5 explicitly separates actions (requests that change things) and rendering (requests that render pages) into two separate requests. Performing an action, such as clicking an action link or submitting a form, results in a client-side redirect to the new page. This is the "Post/Redirect/Get" pattern (alternatively "Post-Then-Redirect", or "Redirect After Post"). This helps ensure that URLs in the browser are book-markable ... but also requires that a bit more information be stored in the session between requests (using the @Persist annotation).

Principle 3 – Differentiate Public vs. Internal APIs

An issue plaguing much ancient versions of Tapestry (4 and earlier) was the lack of a clear delineation between private, internal APIs and public, external APIs. The fact that your code would extend from base objects but that many of the methods on those base objects were "off limits" further confused the issue. This has been identified as a key factor in the "steep learning curve of Tapestry" meme.

Designed from a clean slate, Tapestry 5 is much more ruthless about what is internal vs. external.

First of all, anything inside the org.apache.tapestry5.internal package is internal. It is part of the implementation of Tapestry. It is the man behind the curtain. You should not ever need to directly use this code. It is a bad idea to do so, because internal code may change from one release to the next without concern for backwards compatibility.

Tip

If you ever find yourself forced to make use of internal APIs, please bring it up on the developer mailing list; this is how we know which services should be exposed as public, and fall under the backwards compatibility umbrella.

Principle 4 – Ensure Backwards Compatibility

Older versions of Tapestry were plagued by backwards compatibility problems with every major release. Tapestry 5 did not even attempt to be backwards compatible to Tapestry 4. Instead, it laid the ground work for true backwards compatibility going forwards.

Tapestry 5's API is based largely on naming conventions and annotations. Your components are just ordinary Java classes; you annotate fields to allow Tapestry to maintain their state or to allow Tapestry to inject resources, and you name (or annotate) methods to tell Tapestry under what circumstances a method should be invoked.

Tapestry will adapt to your classes. It will call your methods, passing in values via method parameters. Instead of the rigidness of a fixed interface to implement, Tapestry will simply adapt to your classes, using the hints provided by annotations and simple naming conventions.

Because of this, Tapestry 5 can change internally to a great degree without it affecting any of the application code you write. This has finally cracked the backwards compatibility nut, allowing you to have great assurance that you can upgrade to future releases of Tapestry without breaking your existing applications.

 

Getting started with Tapestry is easy, and you have lots of ways to begin: watch a video, browse the source code of a working demo app, create a skeleton app using Maven, or step through the tutorial.

Watch a short video

For a fast-paced introduction, watch Mark W. Shead's 10 Minute Demo. This video shows how to set up a simple Tapestry application, complete with form validation, Hibernate-based persistence, and Ajax. The video provides a preview of the development speed and productivity that experienced Tapestry users enjoy.

Play with a working demo app

You can also play with Tapestry via our live demonstration applications. To start, have a look at the Hotel Booking Demo. The source code is provided so you can download and play with it.

Create your first Tapestry project

The easiest way to start a new app is to use Apache Maven to create your initial project; Maven can use an archetype (a kind of project template) to create a bare-bones Tapestry application for you.

Once you have Maven installed, execute the following command:

No Format
mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

(Alternatively, if you want to get an archetype for a not-yet-released version of Tapestry – most users don't – you can use the staging URI, https://repository.apache.org/content/repositories/staging ).

Maven will prompt you for the archetype to create ("Tapestry 5 Quickstart Project") and the exact version number (e.g., "5.4.3"). It also asks you for a group id, an artifact id, and a version number. You can see this in the following transcript:

Code Block
languagetext
$ mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO] 
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: http://tapestry.apache.org -> org.apache.tapestry:quickstart (Tapestry 5 Quickstart Project)
2: http://tapestry.apache.org -> org.apache.tapestry:tapestry-archetype (Tapestry 4.1.6 Archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Choose org.apache.tapestry:quickstart version: 
1: 5.0.19
2: 5.1.0.5
3: 5.2.6
4: 5.3.7
5: 5.4.1
Choose a number: 5: 5
Define value for property 'groupId': : com.example
Define value for property 'artifactId': : newapp
Define value for property 'version':  1.0-SNAPSHOT: : 
Define value for property 'package':  com.example: : com.example.newapp
Confirm properties configuration:
groupId: com.example
artifactId: newapp
version: 1.0-SNAPSHOT
package: com.example.newapp
 Y: : Y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: quickstart:5.4.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: newapp
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.newapp
[INFO] Parameter: packageInPathFormat, Value: com/example/newapp
[INFO] Parameter: package, Value: com.example.newapp
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: newapp

[INFO] project created from Archetype in dir: /home/joeuser/junk/junk2/newapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 40.020s
[INFO] Finished at: Sun Apr 09 16:55:01 EDT 2017
[INFO] Final Memory: 16M/303M
[INFO] ------------------------------------------------------------------------


Maven will (after performing a number of one-time downloads) create a skeleton project ready to run. Because we specified an artifactId of "newapp", the project is created in the newapp directory. (Note: if you get "Unable to get resource" warnings at this stage, you may be behind a firewall which blocks outbound HTTP requests to Maven repositories.)

To run the skeleton application, change to the newapp directory and execute the "mvn jetty:run" command to start the Jetty app server:

Code Block
languagebash
$ cd newapp
$ mvn jetty:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building newapp Tapestry 5 Application 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

...

Application 'app' (version 1.0-SNAPSHOT-DEV) startup time: 329 ms to build IoC Registry, 919 ms overall.
 ______                  __             ____
/_  __/__ ____  ___ ___ / /_______ __  / __/
 / / / _ `/ _ \/ -_|_-</ __/ __/ // / /__ \
/_/  \_,_/ .__/\__/___/\__/_/  \_, / /____/
        /_/                   /___/  5.4.1 (development mode)

[INFO] Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server

 

After some more one-time downloads you can open your browser to http://localhost:8080/newapp to see the application running:

Image Added

The application consists of three pages sharing a common look and feel. The initial page, Index, allows you to perform some basic operations.

You can also load the newly-created project it into any IDE and start coding. See the next section on where to find the different components of the application.

Exploring the generated project

The archetype creates the following files:

No Format
newapp/
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── newapp
    │   │               ├── components
    │   │               │   └── Layout.java
    │   │               ├── pages
    │   │               │   ├── About.java
    │   │               │   ├── Contact.java
    │   │               │   ├── Error404.java
    │   │               │   ├── Index.java
    │   │               │   └── Login.java
    │   │               └── services
    │   │                   ├── AppModule.java
    │   │                   ├── DevelopmentModule.java
    │   │                   └── QaModule.java
    │   ├── resources
    │   │   ├── com
    │   │   │   └── example
    │   │   │       └── newapp
    │   │   │           ├── components
    │   │   │           │   └── Layout.tml
    │   │   │           ├── logback.xml
    │   │   │           └── pages
    │   │   │               ├── About.tml
    │   │   │               ├── Contact.tml
    │   │   │               ├── Error404.tml
    │   │   │               ├── Index.properties
    │   │   │               ├── Index.tml
    │   │   │               └── Login.tml
    │   │   └── log4j.properties
    │   └── webapp
    │       ├── WEB-INF
    │       │   ├── app.properties
    │       │   └── web.xml
    │       ├── favicon.ico
    │       ├── images
    │       │   └── tapestry.png
    │       └── mybootstrap
    │           ├── css
    │           │   ├── bootstrap-responsive.css
    │           │   └── bootstrap.css
    │           ├── img
    │           │   ├── glyphicons-halflings-white.png
    │           │   └── glyphicons-halflings.png
    │           └── js
    │               └── bootstrap.js
    ├── site
    │   ├── apt
    │   │   └── index.apt
    │   └── site.xml
    └── test
        ├── conf
        │   ├── testng.xml
        │   └── webdefault.xml
        ├── java
        │   └── PLACEHOLDER
        └── resources
            └── PLACEHOLDER
30 directories, 39 files

A Tapestry application is composed of pages, each page consisting of one template file and one Java class.

Tapestry page templates have the .tml extension and are found within src/main/resources/ under the app's pages package (src/main/resources/com/example/newapp/pages, in this example). Templates are essentially HTML with some special markup to reference properties in the corresponding Java class and to reference ready-made or custom components.

Similarly, Tapestry page classes are found in within the src/main/java under the app's pages package (src/main/java/com/example/newapp/pages, in this example) and their name matches their template name (Index.tml -> Index.java).

In the skeleton project, most of the HTML is not found on the pages themselves but in a Layout component which acts as a global template for the whole site. Java classes for components live in src/main/java/com/example/newapp/components and component templates go in src/main/resources/com/example/newapp/components.

The archetype includes a few optional extras:

  • The bundled version of the Bootstrap CSS library has a per-project override. You can see the files in src/webapp/context/mybootstrap, and the overrides to enable that in AppModule.java.
  • By default, Tapestry users Prototype as its client-side library, the archetype overrides this to jQuery, which is preferred for new projects.
  • The archetype adds a simple filter that shows the timing of each request.
  • The archetype sets up not just for builds with Maven, but also via Gradle.

What's next?

To deepen your understanding, step through the Getting Started, which goes into much more detail about setting up your project as well as loading it into Eclipse... then continues on to teach you more about Tapestry.

Be sure to read about the core Tapestry Principles, and browse the extensive Getting Started.

Obtain Help

Tapestry has an active user mailing list on which you can find a lot of valuable support, commonly within just a few minutes. You can subscribe by sending e-mail to users-subscribe@tapestry.apache.org or look for an answer in the archives. More Options...

Having trouble? Try our Getting Started.

...

 This is already evident in Tapestry 5.1, 5.2 and 5.3 where major new features and improvements have occurred, while remaining 100% backwards compatible to Tapestry 5.0 – as long as you've avoided the temptation to use internal APIs.