Configuring a Data Source In the following sections, we show you how to configure a data source in your application server, such as GlassFish and Tomcat, and how to access the data source from a web application. Configuring a Database Resource in GlassFish GlassFish has a convenient web-based administration interface that you can use to configure a data source. Point your browser to http://localhost:4848 and log on. (The default username is admin and the default password is adminadmin.) First, configure a database pool. Select "Connection Pools" and set up a new pool. Give a name to the pool, select a resource type (javax.sql.DataSource), and pick your database vendor (see Figure 10-1). On the next screen, you specify database connection options such as username and password (see Figure 10-2). Next, you configure a new data source. Give it the name jdbc/mydb and select the pool that you just set up (see Figure 10-3). Finally, you need to place the database driver file (such as postgresql-8.2.jdbc3.jar for the PostgreSQL database) into the domains/domain1/lib/ext subdirectory of your GlassFish installation. Configuring a Database Resource in Tomcat In this section, we walk you through the steps of configuring a database resource pool in the Tomcat 5 container. If you do not use Tomcat, just skip this section. Locate the conf/server.xml file and look for the element that describes the host that will contain your web application, such as <!-- Define the default virtual host --> <Host name="localhost" debug="0" appBase="webapps" unpackWARs="false" autoDeploy="true"> ... </Host> Inside this element, place a DefaultContext element that specifies both the database details (driver, URL, username, and password) and the desired characteristics of the pool. Here is a typical example, specifying a connection pool to a PostgreSQL database. The values that you need to customize are highlighted in bold. <DefaultContext> <Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource"/> <ResourceParams name="jdbc/mydb"> <parameter> <name>factory</name> <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> </parameter> <parameter> <name>driverClassName</name> <value>org.postgresql.Driver</value> </parameter> <parameter> <name>url</name> <value>jdbc:postgresql://127.0.0.1:5432/postgres</value> </parameter> <parameter> <name>username</name> <value>dbuser</value> </parameter> <parameter> <name>password</name> <value>dbpassword</value> </parameter> <parameter> <name>maxActive</name> <value>20</value> </parameter> <parameter> <name>maxIdle</name> <value>10</value> </parameter> <parameter> <name>poolPreparedStatements</name> <value>true</value> </parameter> </ResourceParams> </DefaultContext> Note | You can also add the Resource and ResourceParams elements into the context of a specific web application. Then the data source is available only to that application. | To configure the pool, you specify a sequence of parameters (see Table 10-1 for the most common ones). A complete description of all valid parameters can be found at http://jakarta.apache.org/commons/dbcp/configuration.html. Table 10-1. Common Tomcat Database Pool Parameters Parameter Name | Description | driverClassName | The name of the JDBC driver, such as org.postgresql.Driver | url | The database URL, such as jdbc:postgresql:mydb | username | The database username | password | The password of the database user | maxActive | The maximum number of simultaneous active connections, or zero for no limit | maxIdle | The maximum number of active connections that can remain idle in the pool without extra ones being released, or zero for no limit | poolPreparedStatements | true if prepared statements are pooled (default: false) | removeAbandoned | true if the pool should remove connections that appear to be abandoned (default: false) | removeAbandonedTimeout | The number of seconds after which an unused connection is considered abandoned (default: 300) | logAbandoned | true to log a stack trace of the code that abandoned the connection (default: false) | To activate the pooling of prepared statements, be sure to set poolPreparedStatements to true. The last three parameters in Table 10-1 refer to a useful feature of the Tomcat pool. The pool can be instructed to monitor and remove connections that appear to be abandoned. If a connection has not been used for some time, then it is likely that an application forgot to close it. After all, a web application should always close its database connections after rendering the response to a user request. The pool can recycle unused connections and optionally log these events. The logging is useful for debugging because it allows the application programmer to plug connection leaks. Place the database driver file into Tomcat's common/lib directory. If the database driver file has a .zip extension, you need to rename it to .jar, such as classes12.jar for the Oracle database. Tip | You can find detailed configuration instructions for a number of popular databases at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/jndi-datasource-examples-howto.html. | Accessing a Container-Managed Resource The Java EE specification requires that resources are declared in the web.xml file of your web application. For a data source, add the following entry to your web.xml file: <resource-ref> <res-ref-name>jdbc/mydb</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> Note the name of the resource: jdbc/mydb. That name is used to look up the data source. There are two ways for doing the lookup. The most elegant one is resource injection. You declare a field in a managed bean and mark it with an annotation, like this: @Resource(name="jdbc/mydb") private DataSource source; When the application server loads the managed bean, then the field is automatically initialized. This feature works only in an application server that conforms to the Java EE 5 standard. If you use JSF 1.1 or the standalone Tomcat server, you have to work a little harder, and make a JNDI (Java Naming and Directory Interface) lookup yourself: try { InitialContext ctx = new InitialContext(); source = (DataSource) ctx.lookup("java:comp/env/jdbc/mydb"); } catch (NamingException ex) { . . . } The java:comp/env prefix is the standard JNDI directory lookup path to the component environment in a Java EE container. By convention, you place JDBC resources in the jdbc subpath. It is up to you how to name the individual resources. Caution | GlassFish distinguishes beween "resource reference" names and JNDI names. For most resources, you need to specify a mapping in the sun-web.xml file (see page 489 for an example). However, for JDBC resources, no mapping is required. | Of course, for such a simple lookup, the @Resource annotation is only a minor convenience. However, other resource initializations are more complex. For another example of resource injection, see the section "Using Web Services" on page 516. There, we show you how to inject a web service port with a simple annotation. Without resource injection, we would need to access unsightly helper classes. To be consistent, we like to use the annotation mechanism to initialize all resources. Table 10-2 lists the annotations that you can use to inject resources. Table 10-2. Annotations for Resource Injection Annotation | Resource Type | @Resource @Resources
| Arbitrary JNDI Resource | @WebServiceRef @WebServiceRefs
| Web service port | @EJB @EJBs
| EJB Session Bean | @PersistenceContext @PersistenceContexts
| Persistent Entity Manager | @PersistenceUnit @PersistenceUnits
| Persistent Entity Manager Factory | A Complete Database Example In this example, we show you how to verify a username/password combination. As with the example program in Chapter 1, we start with a simple login screen (Figure 10-4). If the username/password combination is correct, we show a welcome screen (Figure 10-5). Otherwise, we prompt the user to try again (Figure 10-6). Finally, if a database error occurred, we show an error screen (Figure 10-7). Thus, we have four JSF pages, shown in Listing 10-1 through Listing 10-4. Listing 10-5 shows the faces-config.xml file with the navigation rules. The navigation rules use the loginAction and logoutAction properties of the UserBean class. Listing 10-6 gives the code for the UserBean. In our simple example, we add the database code directly into the UserBean class. It would also be possible to have two layers of objects: beans for communication with the JSF pages, and data access objects that represent entities in the database. We place the code for database access into the separate method: public void doLogin() throws SQLException That method queries the database for the username/password combination and sets the loggedIn field to true if the username and password match. The button on the index.jsp page references the login method of the user bean. That method calls the doLogin method and returns a result string for the navigation handler. The login method also deals with exceptions that the doLogin method reports. We assume that the doLogin method is focused on the database, not the user interface. If an exception occurs, doLogin should report it and take no further action. The login method, on the other hand, logs exceptions and returns a result string "internalError" to the navigation handler. public String login() { try { doLogin(); } catch (SQLException ex) { logger.log(Level.SEVERE, "loginAction", ex); return "internalError"; } if (loggedIn) return "loginSuccess"; else return "loginFailure"; } Before running this example, you need to start your database and create a table named Users and add one or more username/password entries: CREATE TABLE Users (username CHAR(20), password CHAR(20)) INSERT INTO Users VALUES ('troosevelt', 'jabberwock') You can then deploy and test your application. Figure 10-8 shows the directory structure for this application, and Figure 10-9 shows the navigation map. The before mentioned application files follow in Listing 10-1 through Listing 10-6. Note | Lots of things can go wrong with database configurations. If the application has an internal error, look at the log file. In GlassFish, the default log file is domains/domain1/logs/server.log. In Tomcat, it is logs/catalina.out. | Listing 10-1. db/web/index.jsp 1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. <f:view> 5. <head> 6. <title><h:outputText value="#{msgs.title}"/></title> 7. </head> 8. <body> 9. <h:form> 10. <h1><h:outputText value="#{msgs.enterNameAndPassword}"/></h1> 11. <h:panelGrid columns="2"> 12. <h:outputText value="#{msgs.name}"/> 13. <h:inputText value="#{user.name}"/> 14. 15. <h:outputText value="#{msgs.password}"/> 16. <h:inputSecret value="#{user.password}"/> 17. </h:panelGrid> 18. <h:commandButton value="#{msgs.login}" action="#{user.login}"/> 19. </h:form> 20. </body> 21. </f:view> 22. </html>
| Listing 10-2. db/web/welcome.jsp 1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. <f:view> 5. <head> 6. <title><h:outputText value="#{msgs.title}"/></title> 7. </head> 8. <body> 9. <h:form> 10. <p> 11. <h:outputText value="#{msgs.welcome}"/> 12. <h:outputText value="#{user.name}"/>! 13. </p> 14. <p> 15. <h:commandButton value="#{msgs.logout}" action="#{user.logout}"/> 16. </p> 17. </h:form> 18. </body> 19. </f:view> 20. </html>
| Listing 10-3. db/web/sorry.jsp 1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. <f:view> 5. <head> 6. <title><h:outputText value="#{msgs.title}"/></title> 7. </head> 8. <body> 9. <h:form> 10. <h1><h:outputText value="#{msgs.authError}"/></h1> 11. <p> 12. <h:outputText value="#{msgs.authError_detail}"/>! 13. </p> 14. <p> 15. <h:commandButton value="#{msgs.continue}" action="login"/> 16. </p> 17. </h:form> 18. </body> 19. </f:view> 20. </html>
| Listing 10-4. db/web/error.jsp 1. <html> 2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4. <f:view> 5. <head> 6. <title><h:outputText value="#{msgs.title}"/></title> 7. </head> 8. <body> 9. <h:form> 10. <h1><h:outputText value="#{msgs.internalError}"/></h1> 11. <p><h:outputText value="#{msgs.internalError_detail}"/></p> 12. <p> 13. <h:commandButton value="#{msgs.continue}" action="login"/> 14. </p> 15. </h:form> 16. </body> 17. </f:view> 18. </html>
| Listing 10-5. db/web/WEB-INF/faces-config.xml 1. <?xml version="1.0"?> 2. <faces-config xmlns="http://java.sun.com/xml/ns/javaee" 3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 5. http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" 6. version="1.2"> 7. <navigation-rule> 8. <from-view-id>/index.jsp</from-view-id> 9. <navigation-case> 10. <from-outcome>loginSuccess</from-outcome> 11. <to-view-id>/welcome.jsp</to-view-id> 12. </navigation-case> 13. <navigation-case> 14. <from-outcome>loginFailure</from-outcome> 15. <to-view-id>/sorry.jsp</to-view-id> 16. </navigation-case> 17. <navigation-case> 18. <from-outcome>internalError</from-outcome> 19. <to-view-id>/error.jsp</to-view-id> 20. </navigation-case> 21. </navigation-rule> 22. <navigation-rule> 23. <from-view-id>/welcome.jsp</from-view-id> 24. <navigation-case> 25. <from-outcome>login</from-outcome> 26. <to-view-id>/index.jsp</to-view-id> 27. </navigation-case> 28. </navigation-rule> 29. <navigation-rule> 30. <from-view-id>/sorry.jsp</from-view-id> 31. <navigation-case> 32. <from-outcome>login</from-outcome> 33. <to-view-id>/index.jsp</to-view-id> 34. </navigation-case> 35. </navigation-rule> 36. <navigation-rule> 37. <from-view-id>/error.jsp</from-view-id> 38. <navigation-case> 39. <from-outcome>login</from-outcome> 40. <to-view-id>/index.jsp</to-view-id> 41. </navigation-case> 42. </navigation-rule> 43. 44. <managed-bean> 45. <managed-bean-name>user</managed-bean-name> 46. <managed-bean-class>com.corejsf.UserBean</managed-bean-class> 47. <managed-bean-scope>session</managed-bean-scope> 48. </managed-bean> 49. 50. <application> 51. <resource-bundle> 52. <base-name>com.corejsf.messages</base-name> 53. <var>msgs</var> 54. </resource-bundle> 55. </application> 56. </faces-config>
| Listing 10-6. db/src/java/com/corejsf/UserBean.java 1. package com.corejsf; 2. 3. import java.sql.Connection; 4. import java.sql.PreparedStatement; 5. import java.sql.ResultSet; 6. import java.sql.SQLException; 7. import java.util.logging.Level; 8. import java.util.logging.Logger; 9. 10. import javax.annotation.Resource; 11. import javax.sql.DataSource; 12. 13. public class UserBean { 14. private String name; 15. private String password; 16. private boolean loggedIn; 17. private Logger logger = Logger.getLogger("com.corejsf"); 18. 19. @Resource(name="jdbc/mydb") 20. private DataSource ds; 21. 22. /* 23. If you use Tomcat or JSF 1.1, remove the @Resource line and add this constructor: 24. public UserBean() 25. { 26. try { 27. Context ctx = new InitialContext(); 28. ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mydb"); 29. } catch (NamingException ex) { 30. logger.log(Level.SEVERE, "DataSource lookup failed", ex); 31. } 32. } 33. */ 34. 35. public String getName() { return name; } 36. public void setName(String newValue) { name = newValue; } 37. 38. public String getPassword() { return password; } 39. public void setPassword(String newValue) { password = newValue; } 40. 41. public String login() { 42. try { 43. doLogin(); 44. } 45. catch (SQLException ex) { 46. logger.log(Level.SEVERE, "login failed", ex); 47. return "internalError"; 48. } 49. if (loggedIn) 50. return "loginSuccess"; 51. else 52. return "loginFailure"; 53. } 54. 55. public String logout() { 56. loggedIn = false; 57. return "login"; 58. } 59. 60. public void doLogin() throws SQLException { 61. if (ds == null) throw new SQLException("No data source"); 62. Connection conn = ds.getConnection(); 63. if (conn == null) throw new SQLException("No connection"); 64. 65. try { 66. PreparedStatement passwordQuery = conn.prepareStatement( 67. "SELECT password from Users WHERE username = ?"); 68. 69. passwordQuery.setString(1, name); 70. 71. ResultSet result = passwordQuery.executeQuery(); 72. 73. if (!result.next()) return; 74. String storedPassword = result.getString("password"); 75. loggedIn = password.equals(storedPassword.trim()); 76. } 77. finally { 78. conn.close(); 79. } 80. } 81. }
| |