forked from seam2/jboss-seam
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Framework.xml
executable file
·601 lines (477 loc) · 22.1 KB
/
Framework.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
<chapter id="framework">
<title>The Seam Application Framework</title>
<para>
Seam makes it really easy to create applications by writing
plain Java classes with annotations, which don't need to extend
any special interfaces or superclasses. But we can simplify
some common programming tasks even further, by providing a set
of pre-built components which can be re-used either by
configuration in <literal>components.xml</literal> (for very
simple cases) or extension.
</para>
<para>
The <emphasis>Seam Application Framework</emphasis> can reduce
the amount of code you need to write when doing basic database
access in a web application, using either Hibernate or JPA.
</para>
<para>
We should emphasize that the framework is extremely simple,
just a handful of simple classes that are easy to understand
and extend. The "magic" is in Seam itself — the same magic
you use when creating any Seam application even without using
this framework.
</para>
<section>
<title>Introduction</title>
<para>
The components provided by the Seam application framework
may be used in one of two different approaches. The first
way is to install and configure an instance of the component
in <literal>components.xml</literal>, just like we have
done with other kinds of built-in Seam components. For
example, the following fragment from
<literal>components.xml</literal> installs a component
which can perform basic CRUD operations for a
<literal>Person</literal> entity:
</para>
<programlisting role="XML"><![CDATA[<framework:entity-home name="personHome"
entity-class="eg.Person"
entity-manager="#{personDatabase}">
<framework:id>#{param.personId}</framework:id>
</framework:entity-home>]]></programlisting>
<para>
If that looks a bit too much like "programming in XML" for
your taste, you can use extension instead:
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In EntityManager personDatabase;
public EntityManager getEntityManager() {
return personDatabase;
}
}]]></programlisting>
<para>
The second approach has one huge advantage: you can easily add
extra functionality, and override the built-in functionality
(the framework classes were carefully designed for extension
and customization).
</para>
<para>
A second advantage is that your classes may be EJB stateful
session beans, if you like. (They do not have to be, they
can be plain JavaBean components if you prefer.) If you are using
JBoss AS, you'll need 4.2.2.GA or later:
</para>
<programlisting role="JAVA"><![CDATA[@Stateful
@Name("personHome")
public class PersonHome extends EntityHome<Person> implements LocalPersonHome {
}]]></programlisting>
<para>
You can also make your classes stateless session beans. In this case
you <emphasis>must</emphasis> use injection to provide the
persistence context, even if it is called
<literal>entityManager</literal>:
</para>
<programlisting role="JAVA"><![CDATA[@Stateless
@Name("personHome")
public class PersonHome extends EntityHome<Person> implements LocalPersonHome {
@In EntityManager entityManager;
public EntityManager getPersistenceContext() {
entityManager;
}
}]]></programlisting>
<para>
At this time, the Seam Application Framework provides four main
built-in components: <literal>EntityHome</literal> and
<literal>HibernateEntityHome</literal> for CRUD, along with
<literal>EntityQuery</literal> and <literal>HibernateEntityQuery</literal>
for queries.
</para>
<para>
The Home and Query components are written so that they can function
with a scope of session, event or conversation. Which scope you
use depends upon the state model you wish to use in your application.
</para>
<para>
The Seam Application Framework only works with Seam-managed
persistence contexts. By default, the components will look
for a persistence context named <literal>entityManager</literal>.
</para>
</section>
<section>
<title>Home objects</title>
<para>
A Home object provides persistence operations for a particular entity
class. Suppose we have our trusty <literal>Person</literal> class:
</para>
<programlisting role="JAVA"><![CDATA[@Entity
public class Person {
@Id private Long id;
private String firstName;
private String lastName;
private Country nationality;
//getters and setters...
}]]></programlisting>
<para>
We can define a <literal>personHome</literal> component either via
configuration:
</para>
<programlisting role="XML"><![CDATA[<framework:entity-home name="personHome" entity-class="eg.Person" />]]></programlisting>
<para>
Or via extension:
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {}]]></programlisting>
<para>
A Home object provides the following operations: <literal>persist()</literal>,
<literal>remove()</literal>, <literal>update()</literal> and
<literal>getInstance()</literal>. Before you can call the
<literal>remove()</literal>, or <literal>update()</literal> operations, you
must first set the identifier of the object you are interested in, using the
<literal>setId()</literal> method.
</para>
<para>
We can use a Home directly from a JSF page, for example:
</para>
<programlisting role="XHTML"><![CDATA[<h1>Create Person</h1>
<h:form>
<div>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
<div>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}"/>
</div>
</h:form>]]></programlisting>
<para>
Usually, it is much nicer to be able to refer to the <literal>Person</literal>
merely as <literal>person</literal>, so let's make that possible by adding a
line to <literal>components.xml</literal>:
</para>
<programlisting role="XML"><![CDATA[<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person" />]]></programlisting>
<para>
(If we are using configuration.)
Or by adding a <literal>@Factory</literal> method to <literal>PersonHome</literal>:
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@Factory("person")
public Person initPerson() { return getInstance(); }
}]]></programlisting>
<para>
(If we are using extension.)
This change simplifies our JSF page to the following:
</para>
<programlisting role="XHTML"><![CDATA[<h1>Create Person</h1>
<h:form>
<div>First name: <h:inputText value="#{person.firstName}"/></div>
<div>Last name: <h:inputText value="#{person.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}"/>
</div>
</h:form>]]></programlisting>
<para>
Well, that lets us create new <literal>Person</literal> entries. Yes,
that is all the code that is required! Now, if we want to be able to
display, update and delete pre-existing <literal>Person</literal>
entries in the database, we need to be able to pass the entry
identifier to the <literal>PersonHome</literal>. Page parameters
are a great way to do that:
</para>
<programlisting role="XML"><![CDATA[<pages>
<page view-id="/editPerson.xhtml">
<param name="personId" value="#{personHome.id}"/>
</page>
</pages>]]></programlisting>
<para>
Now we can add the extra operations to our JSF page:
</para>
<programlisting role="XHTML"><![CDATA[<h1>
<h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
<h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
</h1>
<h:form>
<div>First name: <h:inputText value="#{person.firstName}"/></div>
<div>Last name: <h:inputText value="#{person.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
<h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
<h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
</div>
</h:form>]]></programlisting>
<para>
When we link to the page with no request parameters, the page will
be displayed as a "Create Person" page. When we provide a value for
the <literal>personId</literal> request parameter, it will be an
"Edit Person" page.
</para>
<para>
Suppose we need to create <literal>Person</literal> entries with their
nationality initialized. We can do that easily, via configuration:
</para>
<programlisting role="XML"><![CDATA[<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}"/>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>]]></programlisting>
<para>
Or by extension:
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
}]]></programlisting>
<para>
Of course, the <literal>Country</literal> could be an object managed by
another Home object, for example, <literal>CountryHome</literal>.
</para>
<para>
To add more sophisticated operations (association management, etc), we can
just add methods to <literal>PersonHome</literal>.
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
public void migrate()
{
getInstance().setCountry(country);
update();
}
}]]></programlisting>
<para>
The Home object raises an <literal>org.jboss.seam.afterTransactionSuccess</literal>
event when a transaction succeeds (a call to <literal>persist()</literal>,
<literal>update()</literal> or <literal>remove()</literal> succeeds). By observing
this event we can refresh our queries when the underlying entities are changed. If
we only want to refresh certain queries when a particular entity is persisted,
updated or removed we can observe the
<literal>org.jboss.seam.afterTransactionSuccess.<name></literal>
event (where <literal><name></literal> is the simple name of the entity, e.g. an entity called "org.foo.myEntity" has "myEntity" as simple name).
</para>
<para>
The Home object automatically displays faces messages when an operation is
successful. To customize these messages we can, again, use configuration:
</para>
<programlisting role="XML"><![CDATA[<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}">
<framework:created-message>New person #{person.firstName} #{person.lastName} created</framework:created-message>
<framework:deleted-message>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>
<framework:updated-message>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>
</framework:entity-home>
<component name="newPerson"
class="eg.Person">
<property name="nationality">#{country}</property>
</component>]]></programlisting>
<para>
Or extension:
</para>
<programlisting role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
protected String getCreatedMessage() { return createValueExpression("New person #{person.firstName} #{person.lastName} created"); }
protected String getUpdatedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} updated"); }
protected String getDeletedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} deleted"); }
}]]></programlisting>
<para>
But the best way to specify the messages is to put them in a resource
bundle known to Seam (the bundle named <literal>messages</literal>,
by default).
</para>
<programlisting><![CDATA[Person_created=New person #{person.firstName} #{person.lastName} created
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated]]></programlisting>
<para>
This enables internationalization, and keeps your code and configuration clean of
presentation concerns.
</para>
<para>
The final step is to add validation functionality to the page, using
<literal><s:validateAll></literal> and <literal><s:decorate></literal>,
but I'll leave that for you to figure out.
</para>
</section>
<section>
<title>Query objects</title>
<para>
If we need a list of all <literal>Person</literal> instance in the database, we
can use a Query object. For example:
</para>
<programlisting role="XML"><![CDATA[<framework:entity-query name="people"
ejbql="select p from Person p"/>]]></programlisting>
<para>
We can use it from a JSF page:
</para>
<programlisting role="XHTML"><![CDATA[<h1>List of people</h1>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.xhtml" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable>]]></programlisting>
<para>
We probably need to support pagination:
</para>
<programlisting role="XML"><![CDATA[<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20"/>]]></programlisting>
<para>
We'll use a page parameter to determine the page to display:
</para>
<programlisting role="XML"><![CDATA[<pages>
<page view-id="/searchPerson.xhtml">
<param name="firstResult" value="#{people.firstResult}"/>
</page>
</pages>]]></programlisting>
<para>
The JSF code for a pagination control is a bit verbose, but manageable:
</para>
<programlisting role="XHTML"><![CDATA[<h1>Search for people</h1>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.xhtml" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable>
<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
<f:param name="firstResult" value="0"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
<f:param name="firstResult" value="#{people.previousFirstResult}"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
<f:param name="firstResult" value="#{people.nextFirstResult}"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
<f:param name="firstResult" value="#{people.lastFirstResult}"/>
</s:link>]]></programlisting>
<para>
Real search screens let the user enter a bunch of optional search criteria
to narrow the list of results returned. The Query object lets you specify
optional "restrictions" to support this important usecase:
</para>
<programlisting role="XML"><![CDATA[<component name="examplePerson" class="Person"/>
<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20">
<framework:restrictions>
<value>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
<value>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
</framework:restrictions>
</framework:entity-query>]]></programlisting>
<para>
Notice the use of an "example" object.
</para>
<programlisting role="XHTML"><![CDATA[<h1>Search for people</h1>
<h:form>
<div>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
<div>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
<div><h:commandButton value="Search" action="/search.xhtml"/></div>
</h:form>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.xhtml" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable>]]></programlisting>
<para>
To refresh the query when the underlying entities change we observe the
<literal>org.jboss.seam.afterTransactionSuccess</literal> event:
</para>
<programlisting role="XML"><![CDATA[<event type="org.jboss.seam.afterTransactionSuccess">
<action execute="#{people.refresh}" />
</event>]]></programlisting>
<para>
Or, to just refresh the query when the person entity is persisted, updated or
removed through <literal>PersonHome</literal>:
</para>
<programlisting role="XML"><![CDATA[<event type="org.jboss.seam.afterTransactionSuccess.Person">
<action execute="#{people.refresh}" />
</event>]]></programlisting>
<para>
Unfortunately Query objects don't work well with
<emphasis>join fetch</emphasis> queries - the use of pagination with
these queries is not recommended, and you'll have to implement your own
method of calculating the total number of results (by overriding
<literal>getCountEjbql()</literal>.
</para>
<para>
The examples in this section have all shown reuse by configuration. However,
reuse by extension is equally possible for Query objects.
</para>
</section>
<section>
<title>Controller objects</title>
<para>
A totally optional part of the Seam Application Framework is the class
<literal>Controller</literal> and its subclasses
<literal>EntityController</literal>
<literal>HibernateEntityController</literal> and
<literal>BusinessProcessController</literal>. These classes provide
nothing more than some convenience methods for access to commonly
used built-in components and methods of built-in components. They help
save a few keystrokes (characters can add up!) and provide a great
launchpad for new users to explore the rich functionality built in
to Seam.
</para>
<para>
For example, here is what <literal>RegisterAction</literal> from the
Seam registration example would look like:
</para>
<programlisting role="JAVA"><![CDATA[@Stateless
@Name("register")
public class RegisterAction extends EntityController implements Register
{
@In private User user;
public String register()
{
List existing = createQuery("select u.username from User u where u.username=:username")
.setParameter("username", user.getUsername())
.getResultList();
if ( existing.size()==0 )
{
persist(user);
info("Registered new user #{user.username}");
return "/registered.xhtmlx";
}
else
{
addFacesMessage("User #{user.username} already exists");
return null;
}
}
}]]></programlisting>
<para>
As you can see, its not an earthshattering improvement...
</para>
</section>
</chapter>