123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- ---
- title: Adding JPA To The Address Book Demo
- order: 18
- layout: page
- ---
-
- [[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.
|