General Questions
Contents
- How do I get started with Tapestry?
- Why does Tapestry use Prototype (in versions before 5.4)? Why not insert favorite JavaScript library here?
- Why does Tapestry have its own Inversion of Control Container? Why not Spring or Guice?
- How do I upgrade from Tapestry 4 to Tapestry 5?
- How do I upgrade from one version of Tapestry 5 to another?
- Why are there both Request and HttpServletRequest?
How do I get started with Tapestry?
The easiest way to get started 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. See the Getting Started page for more details.
Even without Maven, Tapestry is quite easy to set up. You just need to download the binaries and setup your build to place them inside your WAR's WEB-INF/lib folder. The rest is just some one-time configuration of the web.xml deployment descriptor.
Why does Tapestry use Prototype (in versions before 5.4)? Why not insert favorite JavaScript library here?
An important goal for Tapestry is seamless DHTML and Ajax integration. To serve that goal, it was important that the built in components be capable of Ajax operations, such as dynamically re-rendering parts of the page. Because of that, it made sense to bundle a well-known JavaScript library as part of Tapestry.
At the time (this would be 2006-ish), Prototype and Scriptaculous were well known and well documented, whereas jQuery was just getting started.
The intent has always been to make this aspect of Tapestry pluggable. Tapestry 5.4 includes the option of either Prototype or jQuery, and future versions of Tapestry will likely remove Prototype as an option..
Why does Tapestry have its own Inversion of Control Container? Why not Spring or Guice?
An Inversion of Control Container is the key piece of Tapestry's infrastructure. It is absolutely necessary to create software as robust, performant and extensible as Tapestry.
Tapestry IoC includes a number of features that distinguish itself from other containers:
- Configured in code, not XML
- Built-in extension mechanism for services: configurations and contributions
- Built-in aspect oriented programming model (service decorations and advice)
- Easy modularization
- Best-of-breed exception reporting
Because Tapestry is implemented on top of its IoC container, and because the container makes it easy to extend or replace any service inside the container, it is possible to make the small changes to Tapestry needed to customize it to any project's needs.
In addition – and this is critical – Tapestry allows 3rd party libraries to be built that fully participate in the configurability of Tapestry itself. This means that such libraries can be configured the same way Tapestry itself is configured, and such libraries can also configure Tapestry itself. This distributed configuration requires an IOC container that fully supports such configurability.
How do I upgrade from Tapestry 4 to Tapestry 5?
There is no existing tool that supports upgrading from Tapestry 4 to Tapestry 5; Tapestry 5 is a complete rewrite.
Many of the basic concepts in Tapestry 4 are still present in Tapestry 5, but refactored, improved, streamlined, and simplified. The basic concept of pages, templates and components are largely the same. Other aspects, such as server-side event handling, is markedly different.
Tapestry 5 is designed so that it can live side-by-side in the same servlet as a Tapestry 4 app, without package namespace conflicts, sharing session data and common resources such as images and CSS. This means that you can gradually migrate a Tapestry 4 app to Tapestry 5 one page (or one portion of the app) at a time.
How do I upgrade from one version of Tapestry 5 to another?
Main Article: How to Upgrade.
A lot of effort goes into making an upgrade from one Tapestry 5 release to another go smoothly. In the general case, it is just a matter of updating the version number in your Maven build.xml
or Gradle build.gradle
file and executing the appropriate commands (e.g., gradle idea
or mvn eclipse:eclipse
) to bring your local workspace up to date with the latest binaries.
After changing dependencies, you should always perform a clean recompile of your application.
We make every effort to ensure backwards-compatibility. Tapestry is mostly coded in terms of interfaces; those interfaces are stable to a point: interfaces your code is expected to implement are usually completely frozen; interfaces your code is expected to invoke, such as the interfaces to IoC services, are stable, but may have new methods added in a release; existing methods are not changed.
In rare cases a choice is necessary between fixing bugs (or adding essential functionality) and maintaining complete backwards compatibility; in those cases, an incompatible change may be introduced. These are always discussed in detail in the Release Notes for the specific release. You should always read the release notes before attempting an upgrade, and always (really, always) be prepared to retest your application afterwards.
Note that you should be careful any time you make use of internal APIs (you can tell an API is internal by the package name, org.apache.tapestry5.internal).
Internal APIs may change at any time; there's no guarantee of backwards compatibility. Please always check on the documentation, or consult the user mailing list, to see if there's a stable, public alternative. If you do make use of internal APIs, be sure to get a discussion going so that your needs can be met in the future by a stable, public API.
Why are there both Request and HttpServletRequest?
Tapestry's Request interface is very close to the standard HttpServletRequest interface. It differs in a few ways, omitting some unneeded methods, and adding a couple of new methods (such as isXHR()
), as well as changing how some existing methods operate. For example, getParameterNames()
returns a sorted List of Strings; HttpServletRequest returns an Enumeration, which is a very dated approach.
However, the stronger reason for Request (and the related interfaces Response and Session) is to enable the support for Portlets at some point in the future. By writing code in terms of Tapestry's Request, and not HttpServletRequest, you can be assured that the same code will operate in both Servlet Tapestry and Portlet Tapestry.
Link Components
Main Articles: Page Navigation, Component Parameters
Contents
How do I add query parameters to a PageLink or ActionLink?
These components do not have parameters to allow you to specify query parameters for the link; they both allow you to specify a context (one or more values to encode into the request path).
However, you can accomplish the same thing with a little code and markup. For example, to create a link to another page and pass a query parameter, you can replace your PageLink component with a standard <a>
tag:
<a href="${profilePageLink}">Display Profile (w/ full details)</a>
In the matching Java class, you can create the Link programmatically:
@Inject private PageRenderLinkSource linkSource; public Link getProfilePageLink() { Link link = linkSource.createPageRenderLinkWithContext(DisplayProfile.class, user); link.addParameterValue("detail", true); return link; }
... and in the DisplayProfile page:
public class DisplayProfile { void onActivate(@RequestParameter("detail") boolean detail) { . . . } }
The @RequestParameter annotation directs Tapestry to extract the query parameter from the request and coerce it to type boolean. You can use any reasonable type for such a parameter (int, long and Date are common).
A similar technique can be used to add query parmeters to component event URLs (the type generated by the ActionLink or EventLink components), by injecting the ComponentResources, and invoking method createEventLink()
.
How do I create a Link back to the current page from a component?
Sometimes it is useful to create a link back to the current page, but you don't always know the name of the page (the link may appear inside a deeply nested subcomponent). Fortunately, this is easy.
<t:pagelink page="prop:componentResources.pageName">refresh page</t:pagelink>
Every component has an extra property, componentResources, added to it: it's the instance of ComponentResources that represents the link between your code and all of Tapestry's structure around your class. One of the properties of ComponentResources is pageName, the name of the page. By binding the PageLink's page parameter with the "prop:" binding prefix, we ensure that we bind to a computed property; this is necessary because the PageLink.page parameter defaults to the "literal:" binding prefix.
As an added benefit, if the page class is ever renamed or moved to a different package, the pageName property will automatically adjust to the new name.
- Client-Side JavaScript (Tapestry 5.4 and newer)
- Legacy JavaScript (prior to Tapestry 5.4)
- CoffeeScript
Injection
Main article: Injection
Contents
- What's the difference between the @Component and @InjectComponent annotations?
- What's the difference between the @InjectPage and @InjectContainer annotations?
- I get an exception because I have two services with the same interface, how do I handle this?
- What's the difference between @Inject and @Environmental?
- But wait ... I see I used the @Inject annotation and it still worked. What gives?
- Ok, but Request is a singleton service, not an environmental, and I can inject that. Is Tapestry really thread safe?
- I use @Inject on a field to inject a service, but the field is still null, what happened?
What's the difference between the @Component
and @InjectComponent
annotations?
The @Component
annotation is used to define the type of component, and its parameter bindings. When using @Component
, the template must not define the type, and any parameter bindings are merged in:
<a t:id="home" class="nav">Back to home</a>
@Component(parameters={ "page=index" }) private PageLink home;
Here the type of component is defined by the field type. The field name is matched against the t:id
in the template. The page
parameter is set in the Java class, and the informal class
parameter is set in the template. If the tag in the template was <t:pagelink>
, or if the template tag included the attribute t:type="pagelink"
, then you would see an exception.
By contrast, @InjectComponent
expects the component to be already defined, and doesn't allow any configuration of it:
<t:form t:id="login"> .... </t:form>
@InjectComponent private Form login;
Again, we're matching the field name to the component id, and you would get an error if the component is not defined in the template.
What's the difference between the @InjectPage
and @InjectContainer
annotations?
The @InjectPage
annotation is used to inject some page in the application into a field of some other page. You often see it used from event handler methods:
@InjectPage private ConfirmRegistration confirmRegistration; Object onSuccessFromRegistrationForm() { confirmRegistration.setStatus("Registration accepted"); confirmRegistration.setValidationCode(userRegistrationData.getValidationCode()); return confirmRegistration; }
This code pattern is used to configure peristent properties of a page before returning it; Tapestry will send a client redirect to the page to present the data.
@InjectContainer
can be used inside a component or a mixin. In a component, it injects the immediate container of the component; this is often the top-level page object.
In a mixin, it injects the component to which the mixin is attached.
I get an exception because I have two services with the same interface, how do I handle this?
It's not uncommon to have two or more services that implement the exact same interface. When you inject, you might start by just identifying the type of service to inject:
@Inject private ComponentEventResultProcessor processor;
Which results in the error: Service interface org.apache.tapestry5.services.ComponentEventResultProcessor is matched by 3 services: AjaxComponentEventResultProcessor, ComponentEventResultProcessor, ComponentInstanceResultProcessor. Automatic dependency resolution requires that exactly one service implement the interface.
We need more information than just the service interface type in order to identify which of the three services to inject. One possibility is to inject with the correct service id:
@InjectService("ComponentEventResultProcessor") private ComponentEventResultProcessor processor;
This works ... but it is clumsy. If the service id, "ComponentEventResultProcessor", ever changes, this code will break. It's not refactoring safe.
Instead, we should use marker annotations. If we look at TapestryModule
, where the ComponentEventResultProcessor service is defined, we'll see it identifies the necessary markers:
@Marker( { Primary.class, Traditional.class }) public ComponentEventResultProcessor buildComponentEventResultProcessor( Map<Class, ComponentEventResultProcessor> configuration) { return constructComponentEventResultProcessor(configuration); }
When a service has marker annotations, the annotations present at the point of injection (the field, method parameter, or constructor parameter) are used to select a matching service. The list of services that match by type is then filtered to only include services that have all of the marker annotations present at the point of injection.
@Inject @Traditional @Primary private ComponentEventResultProcessor processor;
The two marker annotations, @Traditional
and @Primary
, ensure that only a single service matches.
What's the difference between @Inject
and @Environmental
?
@Inject
is relatively general; it can be used to inject resources specific to a page or component (such as ComponentResources, Logger, or Messages), or it can inject services or other objects obtained from the Tapestry IoC container. Once the page is loaded, the values for these injections never change.
@Environmental
is different; it exposes a request-scoped, dynamically bound value:
- "Request scoped": different threads (processing different requests) will see different values when reading the field.
- "Dynamically bound": the value is explicitly placed into the Environment, and can be overridden at any time.
Environmentals are a form of loosely connected communication between an outer component (or even a service) and an inner component. Example: the Form component places a FormSupport
object into the environment. Other components, such as TextField, use the FormSupport
when rendering to perform functions such as allocate unique control names or register client-side validations. The TextField doesn't require that the Form component be the immediate container component, or even an ancestor: a Form on one page may, indirectly, communicate with a TextField on some entirely different page. Neither component directly links to the other, the FormSupport
is the conduit that connects them.
The term "Environmental" was chosen as the value "comes from the environment".
But wait ... I see I used the @Inject
annotation and it still worked. What gives?
In certain cases, Tapestry exposes a service (which can be injected) that is a proxy to the environmental; this is primarily for common environmentals, such as JavaScriptSupport, that may be needed outside of component classes. You can see this in TapestryModule:
/** * Builds a proxy to the current {@link JavaScriptSupport} inside this thread's {@link Environment}. * * @since 5.2.0 */ public JavaScriptSupport buildJavaScriptSupport() { return environmentalBuilder.build(JavaScriptSupport.class); }
This kind of logic is based on the EnvironmentalShadowBuilder service.
Ok, but Request is a singleton service, not an environmental, and I can inject that. Is Tapestry really thread safe?
Yes, of course Tapestry is thread safe. The Request service is another special case, as seen in TapestryModule:
public Request buildRequest() { return shadowBuilder.build(requestGlobals, "request", Request.class); }
RequestGlobals is a per-thread service. The Request service is a global singleton created by the PropertyShadowBuilder service, but is just a proxy. It has no internal state; invoking a method on the Request service just turns around and extracts the Request object from the per-thread RequestGlobals and invokes the same method there.
I use @Inject
on a field to inject a service, but the field is still null, what happened?
This can happen when you use the wrong @Inject
annotation; for example, com.google.inject.Inject instead of org.apache.tapestry5.ioc.annotations.Inject. This can occur when you have TestNG on the classpath, for example, and your IDE is too helpful. Double check your imports when things seem weird.
Also remember that @Inject
on fields works for components and for service implementations or other objects that Tapestry instantiates, but not on arbitrary objects (that are created via Java's new keyword).
Integration with existing applications
Contents
You may have an existing JSP (or Struts, Spring MVC, etc.) application that you want to migrate to Tapestry. It's quite common to do this in stages, moving some functionality into Tapestry and leaving other parts, initially, in the other system. You may need to prevent Tapestry from handling certain requests.
How do I make a form on a JSP submit into Tapestry?
Tapestry's Form component does a lot of work while an HTML form is rendering to store all the information needed to handle the form submission in a later request; this is all very specific to Tapestry and the particular construction of your pages and forms; it can't be reproduced from a JSP.
Fortunately, that isn't necessary: you can have a standard HTML Form submit to a Tapestry page, you just don't get to use all of Tapestry's built in conversion and validation logic.
All you need to know is how Tapestry converts page class names to page names (that appear in the URL). It's basically a matter of stripping off the root-package.pages
prefix from the fully qualified class name. So, for example, if you are building a login screen as a JSP, you might want to have a Tapestry page to receive the user name and password. Let's assume the Tapestry page class is com.example.myapp.pages.LoginForm
; the page name will be loginform (although, since
Tapestry is case insensitive, LoginForm would work just as well), and the URL will be /loginform
.
<form method="post" action="/loginform"> <input type="text" value="userName"/> <br/> <input type="password" value="password"/> <br/> <input type="submit" value="Login"/> </form>
On the Tapestry side, we can expect that the LoginForm page will be activated; this means that its activate event handler will be invoked. We can leverage this, and Tapestry's RequestParameter annotation:
public class LoginForm { void onActivate(@RequestParameter("userName") String userName, @RequestParameter("password") String password) { // Validate and store credentials, etc. } }
The RequestParameter annotation extracts the named query parameter from the request, coerces its type from String to the parameter type (here, also String) and passes it into the method.
How do I share information between a JSP application and the Tapestry application?
From the servlet container's point of view, there's no difference between a servlet, a JSP, and an entire Tapestry application. They all share the same ServletContext, and (once created), the same HttpSession.
On the Tapestry side, it is very easy to read and write session attributes:
public class ShowSearchResults { @SessionAttribute private SearchResults searchResults; }
Reading the instance variable searchResults
is instrumented to instead read the corresponding HttpSession attribute named "searchResults". You can also specify the value
attribute of the SessionAttribute annotation to override the default attribute name.
Writing to the field causes the corresponding HttpSession attribute to be modified.
The session is automatically created as needed.
How do I put the Tapestry application inside a folder, to avoid conflicts?
Support for this was added in 5.3; see the notes on the configuration page.
Limitations
Contents
How do I add new components to an existing page dynamically?
The short answer here is: you don't. The long answer here is you don't have to, to get the behavior you desire.
One of Tapestry basic values is high scalability: this is expressed in a number of ways, reflecting scalability concerns within a single server, and within a cluster of servers.
Although you code Tapestry pages and components as if they were ordinary POJOs (Plain Old Java Objects -- Tapestry does not require you to extend any base classes or implement any special interfaces), as deployed by Tapestry they are closer to a traditional servlet: a single instance of each page services requests from multiple threads. Behind the scenes, Tapestry transforms you code, rewriting it on the fly.
What this means is that any incoming request must be handled by a single page instance. Therefore, Tapestry enforces the concept of static structure, dynamic behavior.
Tapestry provides quite a number of ways to vary what content is rendered, well beyond simple conditionals and loops. It is possible to "drag in" components from other pages when rendering a page (other FAQs will expand on this concept). The point is, that although a Tapestry page's structure is very rigid, the order in which the components of the page render does not have to be top to bottom.
Why doesn't my service implementation reload when I change it?
Main article: Service Implementation Reloading
Live service reloading has some limitations:
- The service must define a service interface.
- The service implementation must be on the file system (not inside a JAR).
- The implementation must be instantiated by Tapestry, not inside code (even code inside a module class).
- The service must use the default scope (reloading of perthread scopes is not supported).
Consider the following example module:
public static void bind(ServiceBinder binder) { binder.bind(ArchiveService.class, ArchiveServiceImpl.class); } public static JobQueue buildJobQueue(MessageService messageService, Map<String,Job> configuration) { JobQueueImpl service = new JobQueueImpl(configuration); messageService.addQueueListener(service); return service; }
ArchiveService is reloadable, because Tapestry instantiates ArchiveServiceImpl
itself. On the other hand, Tapestry invokes buildJobQueue()
and it is your code inside the method that instantiates JobQueueImpl
, so the JobQueue service will not be reloadable.
Finally, only classes whose class files are stored directly on the file system, and not packaged inside JARs, are ever reloadable ... generally, only the services of the application being built (and not services from libraries) will be stored on the file system. This reflects the intent of reloading: as an agile development tool, but not something to be used in deployment.
How do I run multiple Tapestry applications in the same web application?
Running multiple Tapestry 5 applications is not supported; there's only one place to identify the application root package, so even configuring multiple filters into multiple folders will not work.
Support for multiple Tapestry applications in the same web application was a specific non-goal in Tapestry 5 (it needlessly complicated Tapestry 4). Given how loosely connected Tapestry 5 pages are from each other, there doesn't seem to be an advantage to doing so ... and certainly, in terms of memory utilization, there is a significant down side, were it even possible.
You can run a Tapestry 4 app and a Tapestry 5 app side-by-side (the package names are different, for just this reason), but they know nothing of each other, and can't interact directly. This is just like the way you could have a single WAR with multiple servlets; the different applications can only communicate via URLs, or shared state in the HttpSession.
Hibernate Support
Main article: Hibernate
Contents
How do I get Hibernate to startup up when the application starts up, rather than lazily with the first request for the application?
This was a minor problem in 5.0; by 5.1 it is just a matter of overriding the configuration system tapestry.hibernate-early-startup
to "true".
Maven Support
Contents
Why do Maven project names and other details show up in my pages?
Tapestry and maven both use the same syntax for dynamic portions of files: the ${...
} syntax. When Maven is copying resources from src/main/resources
, and when filtering is enabled (which is not the default), then any expansions in Tapestry templates that match against Maven project properties are substituted. If you look at the deployed application you'll see that ${name
} is gone, replaced with your project's name!
The solution is to update your pom.xml
and ignore any .tml files when copying and filtering:
<resource> <directory>src/main/resources</directory> <excludes> <exclude>**/*.tml</exclude> </excludes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.tml</include> </includes> <filtering>false</filtering> </resource>