summaryrefslogtreecommitdiffstats
path: root/documentation/articles/AddingJPAToTheAddressBookDemo.asciidoc
blob: 73a2eb72f7fb7d912b587a884ba66015180fc9b6 (plain)
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
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
[[adding-jpa-to-the-address-book-demo]]
Adding JPA to the address book demo
-----------------------------------

Petter Holmström

[[introduction]]
Introduction
~~~~~~~~~~~~

The https://github.com/vaadin/addressbook/tree/v7[Vaading address book] tutorial (the one
hour version, that is) does a very good job introducing the different
parts of Vaadin. However, it only uses an in-memory data source with
randomly generated data. This may be sufficient for demonstration
purposes, but not for any real world applications that manage data.
Therefore, in this article, we are going to replace the tutorial's
in-memory data source with the Java Persistence API (JPA) and also
utilize some of the new JEE 6 features of
https://glassfish.dev.java.net/[GlassFish] 3.

[[prerequisites]]
Prerequisites
^^^^^^^^^^^^^

In order to fully understand this article, you should be familiar with
JEE and JPA development and you should also have read through the Vaadin
tutorial.

If you want to try out the code in this article you should get the
latest version of GlassFish 3 (build 67 was used for this article) and
http://ant.apache.org[Apache Ant 1.7]. You also need to download the
https://github.com/eriklumme/doc-attachments/blob/master/attachments/addressbook.tar.gz[source code]. *Note, that you have to edit the
_build.xml_ file to point to the correct location of the GlassFish
installation directory before you can use it!*

[[the-system-architecture]]
The System Architecture
~~~~~~~~~~~~~~~~~~~~~~~

The architecture of the application is presented in the following
diagram:

image:img/architecture2.png[System architecture diagram]

In addition to the Vaadin UI created in the tutorial, we will add a
stateless Enterprise Java Bean (EJB) to act as a facade to the database.
The EJB will in turn use JPA to communicate with a JDBC data source (in
this example, the built-in `jdbc/sample` data source).

[[refactoring-the-domain-model]]
Refactoring the Domain Model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Before doing anything else, we have to modify the domain model of the
Address Book example.

[[the-person-class]]
The Person class
^^^^^^^^^^^^^^^^

In order to use JPA, we have to add JPA annotations to the `Person`
class:

[source,java]
....
// Imports omitted
@Entity
public class Person implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Version
  @Column(name = "OPTLOCK")
  private Long version;
  private String firstName = "";
  private String lastName = "";
  private String email = "";
  private String phoneNumber = "";
  private String streetAddress = "";
  private Integer postalCode = null;
  private String city = "";

  public Long getId() {
    return id;
  }

  public Long getVersion() {
    return version;
  }
  // The rest of the methods omitted
}
....

As we do not need to fit the domain model onto an existing database, the
annotations become very simple. We have only marked the class as being
an entity and added an ID and a version field.

[[the-personreference-class]]
The PersonReference class
^^^^^^^^^^^^^^^^^^^^^^^^^

There are many advantages with using JPA or any other Object Persistence
Framework (OPF). The underlying database gets completely abstracted away
and we can work with the domain objects themselves instead of query
results and records. We can detach domain objects, send them to a client
using a remote invocation protocol, then reattach them again.

However, there are a few use cases where using an OPF is not such a good
idea: reporting and listing. When a report is generated or a list of
entities is presented to the user, normally only a small part of the
data is actually required. When the number of objects to fetch is large
and the domain model is complex, constructing the object graphs from the
database can be a very lengthy process that puts the users' patience to
the test – especially if they are only trying to select a person's name
from a list.

Many OPFs support lazy loading of some form, where references and
collections are fetched on demand. However, this rarely works outside
the container, e.g. on the other side of a remoting connection.

One way of working around this problem is to let reports and lists
access the database directly using SQL. This is a fast approach, but it
also couples the code to a particular SQL dialect and therefore to a
particular database vendor.

In this article, we are going to select the road in the middle – we will
only fetch the property values we need instead of the entire object, but
we will use PQL and JPA to do so. In this example, this is a slight
overkill as we have a very simple domain model. However, we do this for
two reasons: Firstly, as Vaadin is used extensively in business
applications where the domain models are complex, we want to introduce
this pattern in an early stage. Secondly, it makes it easier to plug
into Vaadin's data model.

In order to implement this pattern, we need to introduce a new class,
namely `PersonReference`:

[source,java]
....
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.ObjectProperty;
// Some imports omitted

public class PersonReference implements Serializable, Item {
  private Long personId;
  private Map<Object, Property> propertyMap;

  public PersonReference(Long personId, Map<String, Object> propertyMap) {
    this.personId = personId;
    this.propertyMap = new HashMap<Object, Property>();
    for (Map.Entry<Object, Property> entry : propertyMap.entrySet()) {
      this.propertyMap.put(entry.getKey(), new ObjectProperty(entry.getValue()));
    }
  }

  public Long getPersonId() {
    return personId;
  }

  public Property getItemProperty(Object id) {
    return propertyMap.get(id);
  }

  public Collection<?> getItemPropertyIds() {
    return Collections.unmodifiableSet(propertyMap.keySet());
  }

  public boolean addItemProperty(Object id, Property property) {
    throw new UnsupportedOperationException("Item is read-only.");
  }

  public boolean removeItemProperty(Object id) {
    throw new UnsupportedOperationException("Item is read-only.");
  }
}
....

The class contains the ID of the actual `Person` object and a `Map` of
property values. It also implements the `com.vaadin.data.Item`
interface, which makes it directly usable in Vaadin's data containers.

[[the-querymetadata-class]]
The QueryMetaData class
^^^^^^^^^^^^^^^^^^^^^^^

Before moving on to the EJB, we have to introduce yet another class,
namely `QueryMetaData`:

[source,java]
....
// Imports omitted
public class QueryMetaData implements Serializable {

  private boolean[] ascending;
  private String[] orderBy;
  private String searchTerm;
  private String propertyName;

  public QueryMetaData(String propertyName, String searchTerm, String[] orderBy, boolean[] ascending) {
    this.propertyName = propertyName;
    this.searchTerm = searchTerm;
    this.ascending = ascending;
    this.orderBy = orderBy;
  }

  public QueryMetaData(String[] orderBy, boolean[] ascending) {
    this(null, null, orderBy, ascending);
  }

  public boolean[] getAscending() {
    return ascending;
  }

  public String[] getOrderBy() {
    return orderBy;
  }

  public String getSearchTerm() {
    return searchTerm;
  }

  public String getPropertyName() {
    return propertyName;
  }
}
....

As the class name suggests, this class contains query meta data such as
ordering and filtering information. We are going to look at how it is
used in the next section.

[[the-stateless-ejb]]
The Stateless EJB
~~~~~~~~~~~~~~~~~

We are now ready to begin designing the EJB. As of JEE 6, an EJB is no
longer required to have an interface. However, as it is a good idea to
use interfaces at the boundaries of system components, we will create
one nonetheless:

[source,java]
....
// Imports omitted
@TransactionAttribute
@Local
public interface PersonManager {

  public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames);

  public Person getPerson(Long id);

  public Person savePerson(Person person);
}
....

Please note the `@TransactionAttribute` and `@Local` annotations that
instruct GlassFish to use container managed transaction handling, and to
use local references, respectively. Next, we create the implementation:

[source,java]
....
// Imports omitted
@Stateless
public class PersonManagerBean implements PersonManager {

  @PersistenceContext
  protected EntityManager entityManager;

  public Person getPerson(Long id) {
    // Implementation omitted
  }

  public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames) {
    // Implementation omitted
  }

  public Person savePerson(Person person) {
    // Implementation omitted
  }
}
....

We use the `@Stateless` annotation to mark the implementation as a
stateless session EJB. We also use the `@PersistenceContext` annotation
to instruct the container to automatically inject the entity manager
dependency. Thus, we do not have to do any lookups using e.g. JNDI.

Now we can move on to the method implementations.

[source,java]
....
public Person getPerson(Long id) {
  return entityManager.find(Person.class, id);
}
....

This implementation is very straight-forward: given the unique ID, we
ask the entity manager to look up the corresponding `Person` instance
and return it. If no such instance is found, `null` is returned.

[source,java]
....
public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames) {
  StringBuffer pqlBuf = new StringBuffer();
  pqlBuf.append("SELECT p.id");
  for (int i = 0; i < propertyNames.length; i++) {
    pqlBuf.append(",");
    pqlBuf.append("p.");
    pqlBuf.append(propertyNames[i]);
  }
  pqlBuf.append(" FROM Person p");

  if (queryMetaData.getPropertyName() != null) {
    pqlBuf.append(" WHERE p.");
    pqlBuf.append(queryMetaData.getPropertyName());
    if (queryMetaData.getSearchTerm() == null) {
      pqlBuf.append(" IS NULL");
    } else {
      pqlBuf.append(" = :searchTerm");
    }
  }

  if (queryMetaData != null && queryMetaData.getAscending().length > 0) {
    pqlBuf.append(" ORDER BY ");
    for (int i = 0; i < queryMetaData.getAscending().length; i++) {
      if (i > 0) {
        pqlBuf.append(",");
      }
      pqlBuf.append("p.");
      pqlBuf.append(queryMetaData.getOrderBy()[i]);
      if (!queryMetaData.getAscending()[i]) {
        pqlBuf.append(" DESC");
      }
    }
  }

  String pql = pqlBuf.toString();
  Query query = entityManager.createQuery(pql);
  if (queryMetaData.getPropertyName() != null && queryMetaData.getSearchTerm() != null) {
    query.setParameter("searchTerm", queryMetaData.getSearchTerm());
  }

  List<Object[]> result = query.getResultList();
  List<PersonReference> referenceList = new ArrayList<PersonReference>(result.size());

  HashMap<String, Object> valueMap;
  for (Object[] row : result) {
    valueMap = new HashMap<String, Object>();
    for (int i = 1; i < row.length; i++) {
      valueMap.put(propertyNames[i - 1], row[i]);
    }
    referenceList.add(new PersonReference((Long) row[0], valueMap));
  }
  return referenceList;
}
....

This method is a little more complicated and also demonstrates the usage
of the `QueryMetaData` class. What this method does is that it
constructs a PQL query that fetches the values of the properties
provided in the `propertyNames` array from the database. It then uses
the `QueryMetaData` instance to add information about ordering and
filtering. Finally, it executes the query and returns the result as a
list of `PersonReference` instances.

The advantage with using `QueryMetaData` is that additional query
options can be added without having to change the interface. We could
e.g. create a subclass named `AdvancedQueryMetaData` with information
about wildcards, result size limitations, etc.

[source,java]
....
public Person savePerson(Person person) {
  if (person.getId() == null)
    entityManager.persist(person);
  else
    entityManager.merge(person);
  return person;
}
....

This method checks if `person` is persistent or transient, merges or
persists it, respectively, and finally returns it. The reason why
`person` is returned is that this makes the method usable for remote
method calls. However, as this example does not need any remoting, we
are not going to discuss this matter any further in this article.

[[plugging-into-the-ui]]
Plugging Into the UI
~~~~~~~~~~~~~~~~~~~~

The persistence component of our Address Book application is now
completed. Now we just have to plug it into the existing user interface
component. In this article, we are only going to look at some of the
changes that have to be made to the code. That is, if you try to deploy
the application with the changes presented in this article only, it will
not work. For all the changes, please check the source code archive
attached to this article.

[[creating-a-new-container]]
Creating a New Container
^^^^^^^^^^^^^^^^^^^^^^^^

First of all, we have to create a Vaadin container that knows how to
read data from a `PersonManager`:

[source,java]
....
// Imports omitted
public class PersonReferenceContainer implements Container, Container.ItemSetChangeNotifier {

  public static final Object[] NATURAL_COL_ORDER = new String[] {"firstName", "lastName", "email",
      "phoneNumber", "streetAddress", "postalCode", "city"};
  protected static final Collection<Object> NATURAL_COL_ORDER_COLL = Collections.unmodifiableList(
    Arrays.asList(NATURAL_COL_ORDER)
  );
  protected final PersonManager personManager;
  protected List<PersonReference> personReferences;
  protected Map<Object, PersonReference> idIndex;
  public static QueryMetaData defaultQueryMetaData = new QueryMetaData(
    new String[]{"firstName", "lastName"}, new boolean[]{true, true});
  protected QueryMetaData queryMetaData = defaultQueryMetaData;
  // Some fields omitted

  public PersonReferenceContainer(PersonManager personManager) {
    this.personManager = personManager;
  }

  public void refresh() {
    refresh(queryMetaData);
  }

  public void refresh(QueryMetaData queryMetaData) {
    this.queryMetaData = queryMetaData;
    personReferences = personManager.getPersonReferences(queryMetaData, (String[]) NATURAL_COL_ORDER);
    idIndex = new HashMap<Object, PersonReference>(personReferences.size());
    for (PersonReference pf : personReferences) {
      idIndex.put(pf.getPersonId(), pf);
    }
    notifyListeners();
  }

  public QueryMetaData getQueryMetaData() {
    return queryMetaData;
  }

  public void close() {
    if (personReferences != null) {
      personReferences.clear();
      personReferences = null;
    }
  }

  public boolean isOpen() {
    return personReferences != null;
  }

  public int size() {
    return personReferences == null ? 0 : personReferences.size();
  }

  public Item getItem(Object itemId) {
    return idIndex.get(itemId);
  }

  public Collection<?> getContainerPropertyIds() {
    return NATURAL_COL_ORDER_COLL;
  }

  public Collection<?> getItemIds() {
    return Collections.unmodifiableSet(idIndex.keySet());
  }

  public List<PersonReference> getItems() {
    return Collections.unmodifiableList(personReferences);
  }

  public Property getContainerProperty(Object itemId, Object propertyId) {
    Item item = idIndex.get(itemId);
    if (item != null) {
      return item.getItemProperty(propertyId);
    }
    return null;
  }

  public Class<?> getType(Object propertyId) {
    try {
      PropertyDescriptor pd = new PropertyDescriptor((String) propertyId, Person.class);
      return pd.getPropertyType();
    } catch (Exception e) {
      return null;
    }
  }

  public boolean containsId(Object itemId) {
    return idIndex.containsKey(itemId);
  }

  // Unsupported methods omitted
  // addListener(..) and removeListener(..) omitted

  protected void notifyListeners() {
    ArrayList<ItemSetChangeListener> cl = (ArrayList<ItemSetChangeListener>) listeners.clone();
    ItemSetChangeEvent event = new ItemSetChangeEvent() {
      public Container getContainer() {
        return PersonReferenceContainer.this;
      }
    };

    for (ItemSetChangeListener listener : cl) {
      listener.containerItemSetChange(event);
    }
  }
}
....

Upon creation, this container is empty. When one of the `refresh(..)`
methods is called, a list of `PersonReference`s are fetched from the
`PersonManager` and cached locally. Even though the database is updated,
e.g. by another user, the container contents will not change before the
next call to `refresh(..)`.

To keep things simple, the container is read only, meaning that all
methods that are designed to alter the contents of the container throw
an exception. Sorting, optimization and lazy loading has also been left
out (if you like, you can try to implement these yourself).

[[modifying-the-personform-class]]
Modifying the PersonForm class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

We now have to refactor the code to use our new container, starting with
the `PersonForm` class. We begin with the part of the constructor that
creates a list of all the cities currently in the container:

[source,java]
....
PersonReferenceContainer ds = app.getDataSource();
for (PersonReference pf : ds.getItems()) {
  String city = (String) pf.getItemProperty("city").getValue();
  cities.addItem(city);
}
....

We have changed the code to iterate a collection of `PersonReference`
instances instead of `Person` instances.

Then, we will continue with the part of the `buttonClick(..)` method
that saves the contact:

[source,java]
....
if (source == save) {
  if (!isValid()) {
    return;
  }
  commit();
  person = app.getPersonManager().savePerson(person);
  setItemDataSource(new BeanItem(person));
  newContactMode = false;
  app.getDataSource().refresh();
  setReadOnly(true);
}
....

The code has actually become simpler, as the same method is used to save
both new and existing contacts. When the contact is saved, the container
is refreshed so that the new information is displayed in the table.

Finally, we will add a new method, `editContact(..)` for displaying and
editing existing contacts:

[source,java]
....
public void editContact(Person person) {
  this.person = person;
  setItemDataSource(new BeanItem(person))
  newContactMode = false;
  setReadOnly(true);
}
....

This method is almost equal to `addContact()` but uses an existing
`Person` instance instead of a newly created one. It also makes the form
read only, as the user is expected to click an Edit button to make the
form editable.

[[modifying-the-addressbookapplication-class]]
Modifying the AddressBookApplication class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Finally, we are going to replace the old container with the new one in
the main application class. We will start by adding a constructor:

[source,java]
....
public AddressBookApplication(PersonManager personManager) {
  this.personManager = personManager;
}
....

This constructor will be used by a custom application servlet to inject
a reference to the `PersonManager` EJB. When this is done, we move on to
the `init()` method:

[source,java]
....
public void init() {
  dataSource = new PersonReferenceContainer(personManager);
  dataSource.refresh(); // Load initial data
  buildMainLayout();
  setMainComponent(getListView());
}
....

The method creates a container and refreshes it in order to load the
existing data from the database – otherwise, the user would be presented
with an empty table upon application startup.

Next, we modify the code that is used to select contacts:

[source,java]
....
public void valueChange(ValueChangeEvent event) {
  Property property = event.getProperty();
  if (property == personList) {
    Person person = personManager.getPerson((Long) personList.getValue());
    personForm.editContact(person);
  }
}
....

The method gets the ID of the currently selected person and uses it to
lookup the `Person` instance from the database, which is then passed to
the person form using the newly created `editContact(..)` method.

Next, we modify the code that handles searches:

[source,java]
....
public void search(SearchFilter searchFilter) {
  QueryMetaData qmd = new QueryMetaData((String) searchFilter.getPropertyId(), searchFilter.getTerm(),
    getDataSource().getQueryMetaData().getOrderBy(),
    getDataSource().getQueryMetaData().getAscending());
  getDataSource().refresh(qmd);
  showListView();
  // Visual notification omitted
}
....

Instead of filtering the container, this method constructs a new
`QueryMetaData` instance and refreshes the data source. Thus, the search
operation is performed in the database and not in the container itself.

As we have removed container filtering, we also have to change the code
that is used to show all contacts:

[source,java]
....
public void itemClick(ItemClickEvent event) {
  if (event.getSource() == tree) {
    Object itemId = event.getItemId();
    if (itemId != null) {
      if (itemId == NavigationTree.SHOW_ALL) {
        getDataSource().refresh(PersonReferenceContainer.defaultQueryMetaData);
        showListView();
      } else if (itemId == NavigationTree.SEARCH) {
        showSearchView();
      } else if (itemId instanceof SearchFilter) {
        search((SearchFilter) itemId);
      }
    }
  }
}
....

Instead of removing the filters, this method refreshes the data source
using the default query meta data.

[[creating-a-custom-servlet]]
Creating a Custom Servlet
~~~~~~~~~~~~~~~~~~~~~~~~~

The original tutorial used an `ApplicationServlet` configured in
_web.xml_ to start the application. In this version, however, we are
going to create our own custom servlet. By doing this, we can let
GlassFish inject the reference to the `PersonManager` EJB using
annotations, which means that we do not need any JDNI look ups at all.
As a bonus, we get rid of the _web.xml_ file as well thanks to the new
JEE 6 `@WebServlet` annotation. The servlet class can be added as an
inner class to the main application class:

[source,java]
....
@WebServlet(urlPatterns = "/*")
public static class Servlet extends AbstractApplicationServlet {

  @EJB
  PersonManager personManager;

  @Override
  protected Application getNewApplication(HttpServletRequest request) throws ServletException {
    return new AddressBookApplication(personManager);
  }

  @Override
  protected Class<? extends Application> getApplicationClass() throws ClassNotFoundException {
    return AddressBookApplication.class;
  }
}
....

When the servlet is initialized by the web container, the
`PersonManager` EJB will be automatically injected into the
`personManager` field thanks to the `@EJB` annotation. This reference
can then be passed to the main application class in the
`getNewApplication(..)` method.

[[classical-deployment]]
Classical Deployment
~~~~~~~~~~~~~~~~~~~~

Packaging this application into a WAR is no different from the Hello
World example. We just have to remember to include the _persistence.xml_
file (we are not going to cover the contents of this file in this
article), otherwise JPA will not work. Note, that as of JEE 6, we do not
need to split up the application into a different bundle for the EJB and
another for the UI. We also do not need any other configuration files
than the persistence unit configuration file.

The actual packaging can be done using the following Ant target:

[source,xml]
....
<target name="package-with-vaadin" depends="compile">
  <mkdir dir="${dist.dir}"/>
  <war destfile="${dist.dir}/${ant.project.name}-with-vaadin.war" needxmlfile="false">
    <lib file="${vaadin.jar}"/>
    <classes dir="${build.dir}"/>
    <fileset dir="${web.dir}" includes="**"/>
  </war>
</target>
....

Once the application has been packaged, it can be deployed like so,
using the *asadmin* tool that comes with GlassFish:

[source,bash]
....
$ asadmin deploy /path/to/addressbook-with-vaadin.war
....

Note, that the Java DB database bundled with GlassFish must be started
prior to deploying the application. Now we can test the application by
opening a web browser and navigating to
http://localhost:8080/addressbook-with-vaadin. The running application
should look something like this:

image:img/ab-with-vaadin-scrshot.png[Running application screenshot]

[[osgi-deployment-options]]
OSGi Deployment Options
~~~~~~~~~~~~~~~~~~~~~~~

The OSGi support of GlassFish 3 introduces some new possibilities for
Vaadin development. If the Vaadin library is deployed as an OSGi bundle, we can package and
deploy the address book application without the Vaadin library. The
following Ant target can be used to create the WAR:

[source,xml]
....
<target name="package-without-vaadin" depends="compile">
  <mkdir dir="${dist.dir}"/>
  <war destfile="${dist.dir}/${ant.project.name}-without-vaadin.war" needxmlfile="false">
    <classes dir="${build.dir}"/>
    <fileset dir="${web.dir}" includes="**"/>
  </war>
</target>
....

[[summary]]
Summary
~~~~~~~

In this article, we have extended the Address Book demo to use JPA
instead of the in-memory container, with an EJB acting as the facade to
the database. Thanks to annotations, the application does not contain a
single JNDI lookup, and thanks to JEE 6, the application can be deployed
as a single WAR.