You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AddingJPAToTheAddressBookDemo.asciidoc 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. ---
  2. title: Adding JPA To The Address Book Demo
  3. order: 18
  4. layout: page
  5. ---
  6. [[adding-jpa-to-the-address-book-demo]]
  7. = Adding JPA to the address book demo
  8. Petter Holmström
  9. [[introduction]]
  10. Introduction
  11. ~~~~~~~~~~~~
  12. The https://github.com/vaadin/addressbook/tree/v7[Vaading address book] tutorial (the one
  13. hour version, that is) does a very good job introducing the different
  14. parts of Vaadin. However, it only uses an in-memory data source with
  15. randomly generated data. This may be sufficient for demonstration
  16. purposes, but not for any real world applications that manage data.
  17. Therefore, in this article, we are going to replace the tutorial's
  18. in-memory data source with the Java Persistence API (JPA) and also
  19. utilize some of the new JEE 6 features of
  20. https://glassfish.dev.java.net/[GlassFish] 3.
  21. [[prerequisites]]
  22. Prerequisites
  23. ^^^^^^^^^^^^^
  24. In order to fully understand this article, you should be familiar with
  25. JEE and JPA development and you should also have read through the Vaadin
  26. tutorial.
  27. If you want to try out the code in this article you should get the
  28. latest version of GlassFish 3 (build 67 was used for this article) and
  29. http://ant.apache.org[Apache Ant 1.7]. You also need to download the
  30. https://github.com/eriklumme/doc-attachments/blob/master/attachments/addressbook.tar.gz[source code]. *Note, that you have to edit the
  31. _build.xml_ file to point to the correct location of the GlassFish
  32. installation directory before you can use it!*
  33. [[the-system-architecture]]
  34. The System Architecture
  35. ~~~~~~~~~~~~~~~~~~~~~~~
  36. The architecture of the application is presented in the following
  37. diagram:
  38. image:img/architecture2.png[System architecture diagram]
  39. In addition to the Vaadin UI created in the tutorial, we will add a
  40. stateless Enterprise Java Bean (EJB) to act as a facade to the database.
  41. The EJB will in turn use JPA to communicate with a JDBC data source (in
  42. this example, the built-in `jdbc/sample` data source).
  43. [[refactoring-the-domain-model]]
  44. Refactoring the Domain Model
  45. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  46. Before doing anything else, we have to modify the domain model of the
  47. Address Book example.
  48. [[the-person-class]]
  49. The Person class
  50. ^^^^^^^^^^^^^^^^
  51. In order to use JPA, we have to add JPA annotations to the `Person`
  52. class:
  53. [source,java]
  54. ....
  55. // Imports omitted
  56. @Entity
  57. public class Person implements Serializable {
  58. @Id
  59. @GeneratedValue(strategy = GenerationType.IDENTITY)
  60. private Long id;
  61. @Version
  62. @Column(name = "OPTLOCK")
  63. private Long version;
  64. private String firstName = "";
  65. private String lastName = "";
  66. private String email = "";
  67. private String phoneNumber = "";
  68. private String streetAddress = "";
  69. private Integer postalCode = null;
  70. private String city = "";
  71. public Long getId() {
  72. return id;
  73. }
  74. public Long getVersion() {
  75. return version;
  76. }
  77. // The rest of the methods omitted
  78. }
  79. ....
  80. As we do not need to fit the domain model onto an existing database, the
  81. annotations become very simple. We have only marked the class as being
  82. an entity and added an ID and a version field.
  83. [[the-personreference-class]]
  84. The PersonReference class
  85. ^^^^^^^^^^^^^^^^^^^^^^^^^
  86. There are many advantages with using JPA or any other Object Persistence
  87. Framework (OPF). The underlying database gets completely abstracted away
  88. and we can work with the domain objects themselves instead of query
  89. results and records. We can detach domain objects, send them to a client
  90. using a remote invocation protocol, then reattach them again.
  91. However, there are a few use cases where using an OPF is not such a good
  92. idea: reporting and listing. When a report is generated or a list of
  93. entities is presented to the user, normally only a small part of the
  94. data is actually required. When the number of objects to fetch is large
  95. and the domain model is complex, constructing the object graphs from the
  96. database can be a very lengthy process that puts the users' patience to
  97. the test – especially if they are only trying to select a person's name
  98. from a list.
  99. Many OPFs support lazy loading of some form, where references and
  100. collections are fetched on demand. However, this rarely works outside
  101. the container, e.g. on the other side of a remoting connection.
  102. One way of working around this problem is to let reports and lists
  103. access the database directly using SQL. This is a fast approach, but it
  104. also couples the code to a particular SQL dialect and therefore to a
  105. particular database vendor.
  106. In this article, we are going to select the road in the middle – we will
  107. only fetch the property values we need instead of the entire object, but
  108. we will use PQL and JPA to do so. In this example, this is a slight
  109. overkill as we have a very simple domain model. However, we do this for
  110. two reasons: Firstly, as Vaadin is used extensively in business
  111. applications where the domain models are complex, we want to introduce
  112. this pattern in an early stage. Secondly, it makes it easier to plug
  113. into Vaadin's data model.
  114. In order to implement this pattern, we need to introduce a new class,
  115. namely `PersonReference`:
  116. [source,java]
  117. ....
  118. import com.vaadin.data.Item;
  119. import com.vaadin.data.Property;
  120. import com.vaadin.data.util.ObjectProperty;
  121. // Some imports omitted
  122. public class PersonReference implements Serializable, Item {
  123. private Long personId;
  124. private Map<Object, Property> propertyMap;
  125. public PersonReference(Long personId, Map<String, Object> propertyMap) {
  126. this.personId = personId;
  127. this.propertyMap = new HashMap<Object, Property>();
  128. for (Map.Entry<Object, Property> entry : propertyMap.entrySet()) {
  129. this.propertyMap.put(entry.getKey(), new ObjectProperty(entry.getValue()));
  130. }
  131. }
  132. public Long getPersonId() {
  133. return personId;
  134. }
  135. public Property getItemProperty(Object id) {
  136. return propertyMap.get(id);
  137. }
  138. public Collection<?> getItemPropertyIds() {
  139. return Collections.unmodifiableSet(propertyMap.keySet());
  140. }
  141. public boolean addItemProperty(Object id, Property property) {
  142. throw new UnsupportedOperationException("Item is read-only.");
  143. }
  144. public boolean removeItemProperty(Object id) {
  145. throw new UnsupportedOperationException("Item is read-only.");
  146. }
  147. }
  148. ....
  149. The class contains the ID of the actual `Person` object and a `Map` of
  150. property values. It also implements the `com.vaadin.data.Item`
  151. interface, which makes it directly usable in Vaadin's data containers.
  152. [[the-querymetadata-class]]
  153. The QueryMetaData class
  154. ^^^^^^^^^^^^^^^^^^^^^^^
  155. Before moving on to the EJB, we have to introduce yet another class,
  156. namely `QueryMetaData`:
  157. [source,java]
  158. ....
  159. // Imports omitted
  160. public class QueryMetaData implements Serializable {
  161. private boolean[] ascending;
  162. private String[] orderBy;
  163. private String searchTerm;
  164. private String propertyName;
  165. public QueryMetaData(String propertyName, String searchTerm, String[] orderBy, boolean[] ascending) {
  166. this.propertyName = propertyName;
  167. this.searchTerm = searchTerm;
  168. this.ascending = ascending;
  169. this.orderBy = orderBy;
  170. }
  171. public QueryMetaData(String[] orderBy, boolean[] ascending) {
  172. this(null, null, orderBy, ascending);
  173. }
  174. public boolean[] getAscending() {
  175. return ascending;
  176. }
  177. public String[] getOrderBy() {
  178. return orderBy;
  179. }
  180. public String getSearchTerm() {
  181. return searchTerm;
  182. }
  183. public String getPropertyName() {
  184. return propertyName;
  185. }
  186. }
  187. ....
  188. As the class name suggests, this class contains query meta data such as
  189. ordering and filtering information. We are going to look at how it is
  190. used in the next section.
  191. [[the-stateless-ejb]]
  192. The Stateless EJB
  193. ~~~~~~~~~~~~~~~~~
  194. We are now ready to begin designing the EJB. As of JEE 6, an EJB is no
  195. longer required to have an interface. However, as it is a good idea to
  196. use interfaces at the boundaries of system components, we will create
  197. one nonetheless:
  198. [source,java]
  199. ....
  200. // Imports omitted
  201. @TransactionAttribute
  202. @Local
  203. public interface PersonManager {
  204. public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames);
  205. public Person getPerson(Long id);
  206. public Person savePerson(Person person);
  207. }
  208. ....
  209. Please note the `@TransactionAttribute` and `@Local` annotations that
  210. instruct GlassFish to use container managed transaction handling, and to
  211. use local references, respectively. Next, we create the implementation:
  212. [source,java]
  213. ....
  214. // Imports omitted
  215. @Stateless
  216. public class PersonManagerBean implements PersonManager {
  217. @PersistenceContext
  218. protected EntityManager entityManager;
  219. public Person getPerson(Long id) {
  220. // Implementation omitted
  221. }
  222. public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames) {
  223. // Implementation omitted
  224. }
  225. public Person savePerson(Person person) {
  226. // Implementation omitted
  227. }
  228. }
  229. ....
  230. We use the `@Stateless` annotation to mark the implementation as a
  231. stateless session EJB. We also use the `@PersistenceContext` annotation
  232. to instruct the container to automatically inject the entity manager
  233. dependency. Thus, we do not have to do any lookups using e.g. JNDI.
  234. Now we can move on to the method implementations.
  235. [source,java]
  236. ....
  237. public Person getPerson(Long id) {
  238. return entityManager.find(Person.class, id);
  239. }
  240. ....
  241. This implementation is very straight-forward: given the unique ID, we
  242. ask the entity manager to look up the corresponding `Person` instance
  243. and return it. If no such instance is found, `null` is returned.
  244. [source,java]
  245. ....
  246. public List<PersonReference> getPersonReferences(QueryMetaData queryMetaData, String... propertyNames) {
  247. StringBuffer pqlBuf = new StringBuffer();
  248. pqlBuf.append("SELECT p.id");
  249. for (int i = 0; i < propertyNames.length; i++) {
  250. pqlBuf.append(",");
  251. pqlBuf.append("p.");
  252. pqlBuf.append(propertyNames[i]);
  253. }
  254. pqlBuf.append(" FROM Person p");
  255. if (queryMetaData.getPropertyName() != null) {
  256. pqlBuf.append(" WHERE p.");
  257. pqlBuf.append(queryMetaData.getPropertyName());
  258. if (queryMetaData.getSearchTerm() == null) {
  259. pqlBuf.append(" IS NULL");
  260. } else {
  261. pqlBuf.append(" = :searchTerm");
  262. }
  263. }
  264. if (queryMetaData != null && queryMetaData.getAscending().length > 0) {
  265. pqlBuf.append(" ORDER BY ");
  266. for (int i = 0; i < queryMetaData.getAscending().length; i++) {
  267. if (i > 0) {
  268. pqlBuf.append(",");
  269. }
  270. pqlBuf.append("p.");
  271. pqlBuf.append(queryMetaData.getOrderBy()[i]);
  272. if (!queryMetaData.getAscending()[i]) {
  273. pqlBuf.append(" DESC");
  274. }
  275. }
  276. }
  277. String pql = pqlBuf.toString();
  278. Query query = entityManager.createQuery(pql);
  279. if (queryMetaData.getPropertyName() != null && queryMetaData.getSearchTerm() != null) {
  280. query.setParameter("searchTerm", queryMetaData.getSearchTerm());
  281. }
  282. List<Object[]> result = query.getResultList();
  283. List<PersonReference> referenceList = new ArrayList<PersonReference>(result.size());
  284. HashMap<String, Object> valueMap;
  285. for (Object[] row : result) {
  286. valueMap = new HashMap<String, Object>();
  287. for (int i = 1; i < row.length; i++) {
  288. valueMap.put(propertyNames[i - 1], row[i]);
  289. }
  290. referenceList.add(new PersonReference((Long) row[0], valueMap));
  291. }
  292. return referenceList;
  293. }
  294. ....
  295. This method is a little more complicated and also demonstrates the usage
  296. of the `QueryMetaData` class. What this method does is that it
  297. constructs a PQL query that fetches the values of the properties
  298. provided in the `propertyNames` array from the database. It then uses
  299. the `QueryMetaData` instance to add information about ordering and
  300. filtering. Finally, it executes the query and returns the result as a
  301. list of `PersonReference` instances.
  302. The advantage with using `QueryMetaData` is that additional query
  303. options can be added without having to change the interface. We could
  304. e.g. create a subclass named `AdvancedQueryMetaData` with information
  305. about wildcards, result size limitations, etc.
  306. [source,java]
  307. ....
  308. public Person savePerson(Person person) {
  309. if (person.getId() == null)
  310. entityManager.persist(person);
  311. else
  312. entityManager.merge(person);
  313. return person;
  314. }
  315. ....
  316. This method checks if `person` is persistent or transient, merges or
  317. persists it, respectively, and finally returns it. The reason why
  318. `person` is returned is that this makes the method usable for remote
  319. method calls. However, as this example does not need any remoting, we
  320. are not going to discuss this matter any further in this article.
  321. [[plugging-into-the-ui]]
  322. Plugging Into the UI
  323. ~~~~~~~~~~~~~~~~~~~~
  324. The persistence component of our Address Book application is now
  325. completed. Now we just have to plug it into the existing user interface
  326. component. In this article, we are only going to look at some of the
  327. changes that have to be made to the code. That is, if you try to deploy
  328. the application with the changes presented in this article only, it will
  329. not work. For all the changes, please check the source code archive
  330. attached to this article.
  331. [[creating-a-new-container]]
  332. Creating a New Container
  333. ^^^^^^^^^^^^^^^^^^^^^^^^
  334. First of all, we have to create a Vaadin container that knows how to
  335. read data from a `PersonManager`:
  336. [source,java]
  337. ....
  338. // Imports omitted
  339. public class PersonReferenceContainer implements Container, Container.ItemSetChangeNotifier {
  340. public static final Object[] NATURAL_COL_ORDER = new String[] {"firstName", "lastName", "email",
  341. "phoneNumber", "streetAddress", "postalCode", "city"};
  342. protected static final Collection<Object> NATURAL_COL_ORDER_COLL = Collections.unmodifiableList(
  343. Arrays.asList(NATURAL_COL_ORDER)
  344. );
  345. protected final PersonManager personManager;
  346. protected List<PersonReference> personReferences;
  347. protected Map<Object, PersonReference> idIndex;
  348. public static QueryMetaData defaultQueryMetaData = new QueryMetaData(
  349. new String[]{"firstName", "lastName"}, new boolean[]{true, true});
  350. protected QueryMetaData queryMetaData = defaultQueryMetaData;
  351. // Some fields omitted
  352. public PersonReferenceContainer(PersonManager personManager) {
  353. this.personManager = personManager;
  354. }
  355. public void refresh() {
  356. refresh(queryMetaData);
  357. }
  358. public void refresh(QueryMetaData queryMetaData) {
  359. this.queryMetaData = queryMetaData;
  360. personReferences = personManager.getPersonReferences(queryMetaData, (String[]) NATURAL_COL_ORDER);
  361. idIndex = new HashMap<Object, PersonReference>(personReferences.size());
  362. for (PersonReference pf : personReferences) {
  363. idIndex.put(pf.getPersonId(), pf);
  364. }
  365. notifyListeners();
  366. }
  367. public QueryMetaData getQueryMetaData() {
  368. return queryMetaData;
  369. }
  370. public void close() {
  371. if (personReferences != null) {
  372. personReferences.clear();
  373. personReferences = null;
  374. }
  375. }
  376. public boolean isOpen() {
  377. return personReferences != null;
  378. }
  379. public int size() {
  380. return personReferences == null ? 0 : personReferences.size();
  381. }
  382. public Item getItem(Object itemId) {
  383. return idIndex.get(itemId);
  384. }
  385. public Collection<?> getContainerPropertyIds() {
  386. return NATURAL_COL_ORDER_COLL;
  387. }
  388. public Collection<?> getItemIds() {
  389. return Collections.unmodifiableSet(idIndex.keySet());
  390. }
  391. public List<PersonReference> getItems() {
  392. return Collections.unmodifiableList(personReferences);
  393. }
  394. public Property getContainerProperty(Object itemId, Object propertyId) {
  395. Item item = idIndex.get(itemId);
  396. if (item != null) {
  397. return item.getItemProperty(propertyId);
  398. }
  399. return null;
  400. }
  401. public Class<?> getType(Object propertyId) {
  402. try {
  403. PropertyDescriptor pd = new PropertyDescriptor((String) propertyId, Person.class);
  404. return pd.getPropertyType();
  405. } catch (Exception e) {
  406. return null;
  407. }
  408. }
  409. public boolean containsId(Object itemId) {
  410. return idIndex.containsKey(itemId);
  411. }
  412. // Unsupported methods omitted
  413. // addListener(..) and removeListener(..) omitted
  414. protected void notifyListeners() {
  415. ArrayList<ItemSetChangeListener> cl = (ArrayList<ItemSetChangeListener>) listeners.clone();
  416. ItemSetChangeEvent event = new ItemSetChangeEvent() {
  417. public Container getContainer() {
  418. return PersonReferenceContainer.this;
  419. }
  420. };
  421. for (ItemSetChangeListener listener : cl) {
  422. listener.containerItemSetChange(event);
  423. }
  424. }
  425. }
  426. ....
  427. Upon creation, this container is empty. When one of the `refresh(..)`
  428. methods is called, a list of `PersonReference`s are fetched from the
  429. `PersonManager` and cached locally. Even though the database is updated,
  430. e.g. by another user, the container contents will not change before the
  431. next call to `refresh(..)`.
  432. To keep things simple, the container is read only, meaning that all
  433. methods that are designed to alter the contents of the container throw
  434. an exception. Sorting, optimization and lazy loading has also been left
  435. out (if you like, you can try to implement these yourself).
  436. [[modifying-the-personform-class]]
  437. Modifying the PersonForm class
  438. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  439. We now have to refactor the code to use our new container, starting with
  440. the `PersonForm` class. We begin with the part of the constructor that
  441. creates a list of all the cities currently in the container:
  442. [source,java]
  443. ....
  444. PersonReferenceContainer ds = app.getDataSource();
  445. for (PersonReference pf : ds.getItems()) {
  446. String city = (String) pf.getItemProperty("city").getValue();
  447. cities.addItem(city);
  448. }
  449. ....
  450. We have changed the code to iterate a collection of `PersonReference`
  451. instances instead of `Person` instances.
  452. Then, we will continue with the part of the `buttonClick(..)` method
  453. that saves the contact:
  454. [source,java]
  455. ....
  456. if (source == save) {
  457. if (!isValid()) {
  458. return;
  459. }
  460. commit();
  461. person = app.getPersonManager().savePerson(person);
  462. setItemDataSource(new BeanItem(person));
  463. newContactMode = false;
  464. app.getDataSource().refresh();
  465. setReadOnly(true);
  466. }
  467. ....
  468. The code has actually become simpler, as the same method is used to save
  469. both new and existing contacts. When the contact is saved, the container
  470. is refreshed so that the new information is displayed in the table.
  471. Finally, we will add a new method, `editContact(..)` for displaying and
  472. editing existing contacts:
  473. [source,java]
  474. ....
  475. public void editContact(Person person) {
  476. this.person = person;
  477. setItemDataSource(new BeanItem(person))
  478. newContactMode = false;
  479. setReadOnly(true);
  480. }
  481. ....
  482. This method is almost equal to `addContact()` but uses an existing
  483. `Person` instance instead of a newly created one. It also makes the form
  484. read only, as the user is expected to click an Edit button to make the
  485. form editable.
  486. [[modifying-the-addressbookapplication-class]]
  487. Modifying the AddressBookApplication class
  488. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  489. Finally, we are going to replace the old container with the new one in
  490. the main application class. We will start by adding a constructor:
  491. [source,java]
  492. ....
  493. public AddressBookApplication(PersonManager personManager) {
  494. this.personManager = personManager;
  495. }
  496. ....
  497. This constructor will be used by a custom application servlet to inject
  498. a reference to the `PersonManager` EJB. When this is done, we move on to
  499. the `init()` method:
  500. [source,java]
  501. ....
  502. public void init() {
  503. dataSource = new PersonReferenceContainer(personManager);
  504. dataSource.refresh(); // Load initial data
  505. buildMainLayout();
  506. setMainComponent(getListView());
  507. }
  508. ....
  509. The method creates a container and refreshes it in order to load the
  510. existing data from the database – otherwise, the user would be presented
  511. with an empty table upon application startup.
  512. Next, we modify the code that is used to select contacts:
  513. [source,java]
  514. ....
  515. public void valueChange(ValueChangeEvent event) {
  516. Property property = event.getProperty();
  517. if (property == personList) {
  518. Person person = personManager.getPerson((Long) personList.getValue());
  519. personForm.editContact(person);
  520. }
  521. }
  522. ....
  523. The method gets the ID of the currently selected person and uses it to
  524. lookup the `Person` instance from the database, which is then passed to
  525. the person form using the newly created `editContact(..)` method.
  526. Next, we modify the code that handles searches:
  527. [source,java]
  528. ....
  529. public void search(SearchFilter searchFilter) {
  530. QueryMetaData qmd = new QueryMetaData((String) searchFilter.getPropertyId(), searchFilter.getTerm(),
  531. getDataSource().getQueryMetaData().getOrderBy(),
  532. getDataSource().getQueryMetaData().getAscending());
  533. getDataSource().refresh(qmd);
  534. showListView();
  535. // Visual notification omitted
  536. }
  537. ....
  538. Instead of filtering the container, this method constructs a new
  539. `QueryMetaData` instance and refreshes the data source. Thus, the search
  540. operation is performed in the database and not in the container itself.
  541. As we have removed container filtering, we also have to change the code
  542. that is used to show all contacts:
  543. [source,java]
  544. ....
  545. public void itemClick(ItemClickEvent event) {
  546. if (event.getSource() == tree) {
  547. Object itemId = event.getItemId();
  548. if (itemId != null) {
  549. if (itemId == NavigationTree.SHOW_ALL) {
  550. getDataSource().refresh(PersonReferenceContainer.defaultQueryMetaData);
  551. showListView();
  552. } else if (itemId == NavigationTree.SEARCH) {
  553. showSearchView();
  554. } else if (itemId instanceof SearchFilter) {
  555. search((SearchFilter) itemId);
  556. }
  557. }
  558. }
  559. }
  560. ....
  561. Instead of removing the filters, this method refreshes the data source
  562. using the default query meta data.
  563. [[creating-a-custom-servlet]]
  564. Creating a Custom Servlet
  565. ~~~~~~~~~~~~~~~~~~~~~~~~~
  566. The original tutorial used an `ApplicationServlet` configured in
  567. _web.xml_ to start the application. In this version, however, we are
  568. going to create our own custom servlet. By doing this, we can let
  569. GlassFish inject the reference to the `PersonManager` EJB using
  570. annotations, which means that we do not need any JDNI look ups at all.
  571. As a bonus, we get rid of the _web.xml_ file as well thanks to the new
  572. JEE 6 `@WebServlet` annotation. The servlet class can be added as an
  573. inner class to the main application class:
  574. [source,java]
  575. ....
  576. @WebServlet(urlPatterns = "/*")
  577. public static class Servlet extends AbstractApplicationServlet {
  578. @EJB
  579. PersonManager personManager;
  580. @Override
  581. protected Application getNewApplication(HttpServletRequest request) throws ServletException {
  582. return new AddressBookApplication(personManager);
  583. }
  584. @Override
  585. protected Class<? extends Application> getApplicationClass() throws ClassNotFoundException {
  586. return AddressBookApplication.class;
  587. }
  588. }
  589. ....
  590. When the servlet is initialized by the web container, the
  591. `PersonManager` EJB will be automatically injected into the
  592. `personManager` field thanks to the `@EJB` annotation. This reference
  593. can then be passed to the main application class in the
  594. `getNewApplication(..)` method.
  595. [[classical-deployment]]
  596. Classical Deployment
  597. ~~~~~~~~~~~~~~~~~~~~
  598. Packaging this application into a WAR is no different from the Hello
  599. World example. We just have to remember to include the _persistence.xml_
  600. file (we are not going to cover the contents of this file in this
  601. article), otherwise JPA will not work. Note, that as of JEE 6, we do not
  602. need to split up the application into a different bundle for the EJB and
  603. another for the UI. We also do not need any other configuration files
  604. than the persistence unit configuration file.
  605. The actual packaging can be done using the following Ant target:
  606. [source,xml]
  607. ....
  608. <target name="package-with-vaadin" depends="compile">
  609. <mkdir dir="${dist.dir}"/>
  610. <war destfile="${dist.dir}/${ant.project.name}-with-vaadin.war" needxmlfile="false">
  611. <lib file="${vaadin.jar}"/>
  612. <classes dir="${build.dir}"/>
  613. <fileset dir="${web.dir}" includes="**"/>
  614. </war>
  615. </target>
  616. ....
  617. Once the application has been packaged, it can be deployed like so,
  618. using the *asadmin* tool that comes with GlassFish:
  619. [source,bash]
  620. ....
  621. $ asadmin deploy /path/to/addressbook-with-vaadin.war
  622. ....
  623. Note, that the Java DB database bundled with GlassFish must be started
  624. prior to deploying the application. Now we can test the application by
  625. opening a web browser and navigating to
  626. http://localhost:8080/addressbook-with-vaadin. The running application
  627. should look something like this:
  628. image:img/ab-with-vaadin-scrshot.png[Running application screenshot]
  629. [[osgi-deployment-options]]
  630. OSGi Deployment Options
  631. ~~~~~~~~~~~~~~~~~~~~~~~
  632. The OSGi support of GlassFish 3 introduces some new possibilities for
  633. Vaadin development. If the Vaadin library is deployed as an OSGi bundle, we can package and
  634. deploy the address book application without the Vaadin library. The
  635. following Ant target can be used to create the WAR:
  636. [source,xml]
  637. ....
  638. <target name="package-without-vaadin" depends="compile">
  639. <mkdir dir="${dist.dir}"/>
  640. <war destfile="${dist.dir}/${ant.project.name}-without-vaadin.war" needxmlfile="false">
  641. <classes dir="${build.dir}"/>
  642. <fileset dir="${web.dir}" includes="**"/>
  643. </war>
  644. </target>
  645. ....
  646. [[summary]]
  647. Summary
  648. ~~~~~~~
  649. In this article, we have extended the Address Book demo to use JPA
  650. instead of the in-memory container, with an EJB acting as the facade to
  651. the database. Thanks to annotations, the application does not contain a
  652. single JNDI lookup, and thanks to JEE 6, the application can be deployed
  653. as a single WAR.