Teach Yourself J2EE in 21 Days (Sams).

00 0672323842 FM
3/20/02
9:31 AM
Page i
Martin Bond
Dan Haywood
Debbie Law
Andy Longshaw
Peter Roxburgh
Teach Yourself
J2EE
in
21 Days
201 West 103rd St., Indianapolis, Indiana, 46290 USA
00 0672323842 FM
3/20/02
9:31 AM
Page ii
Sams Teach Yourself J2EE in 21 Days
Copyright  2002 by Sams Publishing
All rights reserved. No part of this book shall be reproduced, stored in a
retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information
contained herein. Although every precaution has been taken in the preparation
of this book, the publisher and author assume no responsibility for errors or
omissions. Nor is any liability assumed for damages resulting from the use of
the information contained herein.
Library of Congress Catalog Card Number: 2001098579
00
4
3
2
1
Trademarks
All terms mentioned in this book that are known to be trademarks or service
marks have been appropriately capitalized. Sams Publishing cannot attest to
the accuracy of this information. Use of a term in this book should not be
regarded as affecting the validity of any trademark or service mark.
Warning and Disclaimer
Every effort has been made to make this book as complete and as accurate as
possible, but no warranty or fitness is implied. The information provided is on
an “as is” basis. The authors and the publisher shall have neither liability nor
responsibility to any person or entity with respect to any loss or damages arising from the information contained in this book.
EXECUTIVE EDITOR
Michael Stephens
MANAGING EDITOR
Matt Purcell
Michael Watson
PROJECT EDITOR
Christina Smith
COPY EDITOR
Pat Kinyon
INDEXERS
PROOFREADER
TECHNICAL EDITOR
First Printing: April, 2002
01
DEVELOPMENT EDITOR
Melissa Lynch
Printed in the United States of America
02
Todd Green
Tom Dinse
Erika Millen
International Standard Book Number: 0-672-32384-2
03
ACQUISITIONS EDITOR
Harold Finz, Steve Heckler,
Farooq Karim, and Ari
Krupnikov
TEAM COORDINATOR
Pamalee Nelson
INTERIOR DESIGNER
Gary Adair
COVER DESIGNER
Aren Howell
PRODUCTION
Cheryl Lynch
Michelle Mitchell
00 0672323842 FM
3/20/02
9:31 AM
Page iii
Contents at a Glance
Introduction
1
WEEK 1 Introducing J2EE and EJBs
Day 1 The Challenge of N-Tier Development
2 The J2EE Platform and Roles
3 Naming and Directory Services
4 Introduction to EJBs
5 Session EJBs
6 Entity EJBs
7 CMP and EJB QL
7
9
27
81
125
165
211
271
WEEK 2 Developing J2EE Applications
Day 8 Transactions and Persistence
9 Java Message Service
10 Message-Driven Beans
11 JavaMail
12 Servlets
13 JavaServer Pages
14 JSP Tag Libraries
333
335
395
429
461
501
555
603
WEEK 3 Integrating J2EE into the Enterprise
Day 15 Security
16 Integrating XML with J2EE
17 Transforming XML Documents
18 Patterns
19 Integrating with External Resources
20 Using RPC-Style Web Services with J2EE
21 Web Service Registries and Message-Style Web Services
651
653
701
741
787
827
869
923
Appendixes
Appendix A An Introduction to UML
B SQL Reference
C An Overview of XML
D The Java Community Process
Glossary
Index
965
977
987
999
1003
1025
00 0672323842 FM
3/20/02
9:31 AM
Page iv
Contents
Introduction
1
WEEK 1 Introducing J2EE and EJBs
DAY 1 The Challenge of N-Tier Development
7
9
Monolithic Development ......................................................................................10
Consequences of Monolithic Applications ......................................................10
The Move into the Second Tier ............................................................................11
Consequences of the 2-Tier Design ................................................................12
Complexity Simplified by Modularity ................................................................14
Component Technology ..................................................................................15
Benefits of Modularity ....................................................................................16
Benefits of the 3-Tier Scenario ............................................................................16
A Model for Enterprise Computing ....................................................................17
Lifecycle ..........................................................................................................18
Persistence ......................................................................................................18
Naming ............................................................................................................18
Transaction ......................................................................................................19
Java 2 Enterprise Edition (J2EE) ..........................................................................20
Components and Containers ............................................................................20
J2EE Standard Services ..................................................................................21
J2EE Blueprints ..............................................................................................23
J2EE Compatibility Test Suite ........................................................................24
The Future of J2EE ..............................................................................................25
Summary ..............................................................................................................25
Q&A ......................................................................................................................25
Exercises ..............................................................................................................26
DAY 2 The J2EE Platform and Roles
27
Revisiting the J2EE Platform ..............................................................................28
Using Sun Microsystems’ J2EE SDK ..................................................................28
Installing J2EE SDK 1.3 ................................................................................29
Starting the J2EE Reference Implementation (RI) ........................................32
Troubleshooting J2EE and Cloudscape ..........................................................34
Closing Down J2EE RI and Cloudscape ........................................................37
Optional Software Used in this Book ............................................................37
Understanding Tiers and Components ..................................................................38
The Business Tier ..........................................................................................39
The Presentation Tier ......................................................................................44
00 0672323842 FM
3/20/02
9:31 AM
Page v
Components: Web-Centric ..............................................................................45
The Client Tier ................................................................................................49
Standalone Client ............................................................................................52
Understanding Containers ....................................................................................55
Understanding the Services Containers Supply to Components ..........................56
Hypertext Transfer Protocol (HTTP) ..............................................................57
HTTP over Secure Sockets Layer (HTTPS) ..................................................57
Java Database Connectivity (JDBC) ..............................................................57
Java Transaction API (JTA) ............................................................................58
Java Authentication and Authorization Service (JAAS) ................................58
Java API for XML Parsing (JAXP) ................................................................58
Java Naming and Directory Interface (JNDI) ................................................59
JavaBeans Activation Framework (JAF) ........................................................59
JavaMail ..........................................................................................................60
Java Message Service (JMS) ..........................................................................60
Java Interface Definition Language (Java IDL) ..............................................60
Remote Method Invocation over Internet Inter-Orb Protocol (RMI-IIOP) ....61
Connector Architecture ....................................................................................62
Introducing Platform Roles ..................................................................................62
J2EE Product Provider ....................................................................................63
Application Component Provider ....................................................................63
Application Assembler ....................................................................................63
Application Deployer ......................................................................................64
Systems Administrator ....................................................................................64
Tool Provider ..................................................................................................65
Future of J2EE tools ........................................................................................65
Packaging and Deploying J2EE Applications ......................................................66
J2EE applications ..........................................................................................67
Breaking Modules down into Components ....................................................68
Summary ..............................................................................................................70
Q&A ......................................................................................................................70
Exercises—Case Study ........................................................................................71
The Job Agency ..............................................................................................72
Using the Agency Case Study ........................................................................73
Practice Makes Perfect ....................................................................................75
The Case Study Directory on the CD-ROM ..................................................76
Installing the Case Study Database ................................................................76
DAY 3 Naming and Directory Services
81
Naming and Directory Services ............................................................................82
Why Use a Naming Service? ................................................................................82
What is JNDI? ......................................................................................................83
00 0672323842 FM
vi
3/20/02
9:31 AM
Page vi
Sams Teach Yourself J2EE in 21 Days
Common Naming Services ..................................................................................83
Naming Conventions ............................................................................................84
Using JNDI ..........................................................................................................85
Using Sun Microsystems’ J2EE Reference Implementation ..........................85
Obtaining an Initial Context ................................................................................86
Initial Context Naming Exceptions ......................................................................86
Defining the JNDI Service ..............................................................................87
JNDI Properties Files ......................................................................................88
Application Properties ....................................................................................89
Applet Parameters ..........................................................................................90
Hard-Coded Properties ....................................................................................90
Binding JNDI Objects ..........................................................................................90
Binding Objects ..............................................................................................91
Binding Problems ............................................................................................91
Name Persistence ............................................................................................92
Rebinding Objects ..........................................................................................92
Unbinding Objects ..........................................................................................92
Renaming Objects ..........................................................................................93
JNDI Name Lookup ..............................................................................................93
Changing Contexts ..........................................................................................94
Narrowing RMI-IIOP Objects ........................................................................95
Contexts ................................................................................................................96
Listing Contexts ..............................................................................................96
Creating and Destroying Contexts ..................................................................98
More on JNDI Names ........................................................................................100
Special Characters ........................................................................................100
Composite and Compound Names ................................................................100
URLs ..............................................................................................................101
Attributes ............................................................................................................102
Overview of LDAP X.500 Names ................................................................102
Obtaining an LDAP Server ..........................................................................103
Using OpenLDAP ........................................................................................104
Configuring JNDI to use LDAP ....................................................................106
Testing the LDAP Server ..............................................................................107
Obtaining a Directory Context ......................................................................108
Reading Attributes ........................................................................................108
Searching for Objects ....................................................................................109
Manipulating Attributes ................................................................................112
More on Objects ................................................................................................114
Loading Classes from a Code Base ..............................................................114
Defining a Code Base ....................................................................................114
References ....................................................................................................117
00 0672323842 FM
3/20/02
9:31 AM
Page vii
Contents
vii
What Else Can JNDI Do? ..................................................................................120
JNDI Events ..................................................................................................120
Security ..........................................................................................................121
Summary ............................................................................................................122
Q&A ....................................................................................................................123
Exercise ..............................................................................................................124
DAY 4 Introduction to EJBs
125
What Is an EJB? ................................................................................................126
Beans, Clients, Containers, and Servers ........................................................126
The EJB Landscape ......................................................................................127
Discovering EJBs ..........................................................................................127
Types of EJB ................................................................................................128
Common Uses of EJBs ..................................................................................128
Why Use EJBs? ..................................................................................................129
Hiding Complexity ........................................................................................130
Separation of Business Logic from UI and Data Access ..............................130
Container Services ........................................................................................131
What’s in an EJB? ..............................................................................................132
The Business Interface ..................................................................................132
The Business Logic ......................................................................................134
Factory Information ......................................................................................140
Bean Metadata ..............................................................................................141
How Do I Create an EJB? ..................................................................................142
The Creation Mechanism ..............................................................................142
Caveats on Code Creation ............................................................................143
Create the Deployable Component ..............................................................143
How Do I Deploy an EJB? ................................................................................147
Plugging into the Container ..........................................................................147
Performing the Deployment ..........................................................................148
How Do I Use an EJB? ......................................................................................148
Discovery ......................................................................................................148
Retrieval and Use ..........................................................................................149
Disposing of the EJB ....................................................................................150
Running the Client ........................................................................................150
Deploying and Using an EJB in the J2EE Reference Implementation ..............151
Opening the Case Study EAR File ................................................................152
Examining the Case Study Application ........................................................154
Deploying the Case Study Application ........................................................156
Testing the Case Study Application ..............................................................158
Troubleshooting the Case Study Application ................................................160
00 0672323842 FM
viii
3/20/02
9:31 AM
Page viii
Sams Teach Yourself J2EE in 21 Days
Summary ............................................................................................................161
Q&A ....................................................................................................................161
Exercises ............................................................................................................162
DAY 5 Session EJBs
165
Overview ............................................................................................................165
The javax.ejb Package for Session Beans ........................................................167
Stateless Session Bean Lifecycle ........................................................................168
Specifying a Stateless Session Bean ..................................................................172
Implementing a Stateless Session Bean ............................................................175
Implementing javax.ejb.SessionBean ........................................................175
Implementing the Home Interface Methods ................................................175
Implementing the Remote Interface Methods ..............................................177
Exceptions ....................................................................................................179
Configuring and Deploying a Stateless Session Bean ......................................180
Using deploytool ..........................................................................................181
Structural Elements ......................................................................................182
Presentational Elements ................................................................................183
Session Element ............................................................................................184
Deploying the Enterprise Application ..........................................................193
Stateful Session Bean Lifecycle ........................................................................193
Specifying a Stateful Session Bean ....................................................................196
Implementing a Stateful Session Bean ..............................................................198
Passivation ....................................................................................................198
Timeouts ........................................................................................................199
Chaining State ..............................................................................................200
Configuring and Deploying a Stateful Session Bean ........................................200
Client’s View ......................................................................................................201
Patterns and Idioms ............................................................................................202
Business Interface ..........................................................................................203
Adapter ..........................................................................................................204
Coarse-Grained ..............................................................................................205
Gotchas ..............................................................................................................205
Summary ............................................................................................................206
Q&A ....................................................................................................................207
Exercises ............................................................................................................207
DAY 6 Entity EJBs
211
Overview ............................................................................................................211
The N-tier Architecture Revisited ................................................................212
Comparison with RDBMS Technology ........................................................213
Identifying Entities ........................................................................................214
00 0672323842 FM
3/20/02
9:31 AM
Page ix
Contents
ix
The javax.ejb Package for Entity Beans ..........................................................216
Entity Bean Types ..............................................................................................217
Remote Versus Local Interfaces ........................................................................217
BMP Entity Bean Lifecycle ................................................................................219
Specifying a BMP Entity Bean ..........................................................................225
Local-Home Interface ....................................................................................225
Local Interface ..............................................................................................230
Implementing a BMP Entity Bean ....................................................................231
Implementing the Local-Home Interface Methods ......................................235
Implementing the Local Interface Methods ..................................................241
Generating IDs ..............................................................................................243
Granularity Revisited ....................................................................................245
Beware Those Finder Methods! ....................................................................245
EJB Container Performance Tuning ..............................................................247
Configuring and Deploying a BMP Entity Bean ..............................................248
Entity Element ..............................................................................................249
Client’s View ......................................................................................................252
Session Beans Revisited ....................................................................................254
Patterns and Idioms ............................................................................................258
Interfaces, Façades, and State ......................................................................258
Use Local Interfaces for Entity Beans ..........................................................258
Dependent Value Classes ..............................................................................259
Self-Encapsulate Fields ................................................................................261
Don’t Use Enumeration for Finders ..............................................................262
Acquire Late, Release Early ..........................................................................262
Business Interface Revisited ........................................................................264
Gotchas ..............................................................................................................264
Summary ............................................................................................................265
Q&A ....................................................................................................................266
Exercises ............................................................................................................266
DAY 7 CMP and EJB QL
271
Overview of Container-Managed Persistence ....................................................271
N-tier Architecture (Revisited Again) and CMP Fields ................................273
A Quick Word about the Case Study Database ............................................276
CMP Entity Bean Lifecycle ................................................................................277
Container-Managed Relationships ......................................................................279
Relationship Types ........................................................................................280
Navigability ..................................................................................................282
cmr-fields ....................................................................................................282
Manipulating Relationships ..........................................................................286
00 0672323842 FM
x
3/20/02
9:31 AM
Page x
Sams Teach Yourself J2EE in 21 Days
EJB QL ..............................................................................................................291
Select Methods ..............................................................................................291
Syntax and Examples ....................................................................................293
Further Notes ................................................................................................300
Specifying a CMP Entity Bean ..........................................................................301
The Local-Home Interface ............................................................................301
The Local Interface ......................................................................................301
Implementing a CMP Entity Bean ....................................................................302
Implementing javax.ejb.EntityBean ..........................................................302
Implementing the Local-Home Interface Methods ......................................305
Finder Methods ..............................................................................................308
Implementing the Local Interface Methods ..................................................312
Configuring a CMP Entity Bean ........................................................................313
The entity Element ......................................................................................313
The relationships Element ........................................................................317
Deploying a CMP Entity Bean ..........................................................................322
Patterns and Idioms ............................................................................................323
Normalize/Denormalize Data in ejbLoad()/ejbStore() ............................323
Don’t Expose cmp-fields ............................................................................324
Don’t Expose cmr-fields ............................................................................325
Enforce Referential Integrity Through the Bean’s Interface ........................326
Use Select Methods to Implement Home Methods ......................................327
Gotchas ..............................................................................................................328
Summary ............................................................................................................329
Q&A ....................................................................................................................329
Exercises ............................................................................................................330
WEEK 2 Developing J2EE Applications
DAY 8 Transactions and Persistence
333
335
Overview of Transactions ..................................................................................336
Container-Managed Transaction Demarcation ..................................................338
Bean Managed Transaction Demarcation ..........................................................345
Motivation and Restrictions ..........................................................................345
Using the Java Transaction API ....................................................................345
Deploying a BMTD Bean ............................................................................349
Client-Demarcated Transactions ..................................................................350
Exceptions Revisited ....................................................................................350
Extended Stateful Session Bean Lifecycle ........................................................352
Transactions: Behind the Scenes ........................................................................354
Transaction Managers, Resource Managers, and 2PC ..................................354
The JTA API ..................................................................................................356
00 0672323842 FM
3/20/02
9:31 AM
Page xi
Contents
xi
What If It Goes Wrong? ................................................................................359
JTA Versus JTS ..............................................................................................361
Overview of Persistence Technologies ..............................................................363
JDBC ..................................................................................................................365
SQLj ....................................................................................................................367
SQLj Part 0 ....................................................................................................368
SQLj Part 1 ....................................................................................................373
SQLj Part 2 ....................................................................................................378
JDO ....................................................................................................................383
JDO Concepts ................................................................................................384
javax.jdo Classes and Interfaces ................................................................387
Queries ..........................................................................................................389
Other Features ..............................................................................................391
Gotchas ..............................................................................................................392
Summary ............................................................................................................393
Q&A ....................................................................................................................393
Exercises ............................................................................................................394
DAY 9 Java Message Service
395
Messaging ..........................................................................................................395
Message Passing ..........................................................................................396
Java Message Service API ..................................................................................397
JMS and J2EE ..............................................................................................398
JMS API Architecture ........................................................................................399
Message Domains ..........................................................................................400
Developing JMS Applications Using JBoss1 ....................................................402
JMS Implementation in JBoss ......................................................................402
Programming a JMS Application Using J2EE RI ..............................................404
J2EE RI Connection Factories ......................................................................404
Adding Destinations in J2EE RI ..................................................................404
Creating a Queue in J2EE RI ........................................................................404
Point-to-Point Messaging Example ....................................................................406
JMS Messages ..............................................................................................407
Creating a Message ......................................................................................409
Sending a Message ........................................................................................409
Closing the Connection ................................................................................410
Send JMS Text Message Example ................................................................410
Consuming Messages ....................................................................................411
Simple Synchronous Receiver Example ............................................................412
Receive JMS Text Message Example ..........................................................413
Asynchronous Messaging ............................................................................414
The Publish/Subscribe Message Domain ..........................................................415
00 0672323842 FM
xii
3/20/02
9:31 AM
Page xii
Sams Teach Yourself J2EE in 21 Days
Point-to-Point Messaging Example ....................................................................416
Bulletin Board Publisher ..............................................................................417
Bulletin Board Subscriber ............................................................................418
Creating Durable Subscriptions ....................................................................420
Additional JMS Features ..............................................................................422
Introduction to XML ..........................................................................................425
What Is XML and Why Would You Use It? ..............................................425
Summary ............................................................................................................426
Q&A ....................................................................................................................426
Exercise ..............................................................................................................427
DAY 10 Message-Driven Beans
429
What Are Message-Driven Beans? ....................................................................430
The Message Producer’s View ......................................................................430
Similarities and Differences with Other EJBs ..............................................431
Programming Interfaces in a Message-Driven Bean ..........................................431
Life Cycle of a Message-Driven Bean ..............................................................432
The Message-Driven Bean Context ..............................................................433
Creating a Message-Driven Bean ......................................................................434
Method-Ready Pool ......................................................................................434
The Demise of the Bean ................................................................................435
Consuming Messages ....................................................................................435
Handling Exceptions ....................................................................................436
Container- and Bean-Managed Transactions ..............................................436
Message Acknowledgment ............................................................................437
JMS Message Selectors ................................................................................438
Writing a Simple Message-Driven Bean ............................................................439
Implementing the Interfaces ........................................................................439
Running the Example ........................................................................................440
Creating the Queue ........................................................................................441
Deploying the Message-Driven Bean ............................................................442
Create a Sender Client to Create a Message ................................................445
Developing the Agency Case Study Example ..................................................447
Step 1—Sender Helper Class ......................................................................447
Step 2—Agency and Register Session Bean ................................................449
Step 3—The Message-Driven Bean ............................................................451
Step 4—Create the JMS Queue ....................................................................456
Step 5—Deploy the EJBS ............................................................................456
Step 6—Testing the ApplicantMatch Bean ..................................................457
Using Other Architectures ..................................................................................457
Summary ............................................................................................................458
Q&A ....................................................................................................................458
Exercise ..............................................................................................................458
00 0672323842 FM
3/20/02
9:31 AM
Page xiii
Contents
DAY 11 JavaMail
xiii
461
Understanding E-Mail ........................................................................................462
SMTP ............................................................................................................463
Post Office Protocol 3 (POP3) ......................................................................463
Internet Message Access Protocol (IMAP) ..................................................464
Other Protocols ..............................................................................................464
Multipurpose Internet Mail Extensions (MIME) ..........................................464
Introducing the JavaMail API ............................................................................465
Setting up Your Development Environment ......................................................465
Sending a First E-mail ........................................................................................466
Creating a First E-mail ..................................................................................466
Creating Multi-Media E-mails ............................................................................472
Creating the Message: Approach #1 ............................................................472
Creating the Message: Approach #2 ............................................................476
Sending E-mails with Attachments ....................................................................482
Exploring the JavaMail API ................................................................................485
Retrieving Messages ......................................................................................485
Deleting Messages ........................................................................................489
Getting Attachments ......................................................................................490
Authenticating Users and Security ................................................................494
Summary ............................................................................................................497
Q&A ....................................................................................................................497
Exercises ............................................................................................................499
DAY 12 Servlets
501
The Purpose and Use of Servlets ......................................................................502
Tailored for Web Applications ....................................................................502
Server and Platform Independence ..............................................................503
Efficient and Scalable ....................................................................................503
Servlets Integration with the Server ..............................................................503
Introduction to HTTP ........................................................................................504
HTTP Structure ............................................................................................504
Other HTTP Methods ....................................................................................507
Server Responses ..........................................................................................507
Introduction to HTML ........................................................................................509
The Servlet Environment ....................................................................................513
Servlet Containers ........................................................................................513
The Servlet Class Hierarchy ..............................................................................513
Simple Servlet Example ....................................................................................514
Passing Parameter Data to a Servlet ..................................................................519
How to Access Parameters ............................................................................519
Servlet Example with Parameters ................................................................520
00 0672323842 FM
xiv
3/20/02
9:31 AM
Page xiv
Sams Teach Yourself J2EE in 21 Days
Using a POST Request ..................................................................................522
The Servlet Lifecycle ....................................................................................522
The Servlet Context ......................................................................................524
Web Applications ................................................................................................525
Web Application Files and Directory Structure ..........................................525
The Web Application Deployment Descriptor ..............................................526
Handling Errors ..................................................................................................528
HTTP Errors ..................................................................................................528
Servlet Exception Handling ..........................................................................529
Retaining Client and State Information ..............................................................530
Using Session Objects ..................................................................................530
Hidden Form Fields ......................................................................................532
Cookies ..........................................................................................................532
Creating a Cookie ........................................................................................533
URL Rewriting ..............................................................................................535
Servlet Filtering ..................................................................................................535
Programming Filters ......................................................................................535
Example Auditing Filter ................................................................................537
Deploying Filters ..........................................................................................538
Event Listening ..................................................................................................541
Deploying the Listener ..................................................................................543
Servlet Threads ..................................................................................................545
Security and the Servlet Sandbox ......................................................................546
Agency Case Study ............................................................................................546
AgencyTable Servlet Code ............................................................................546
Deploying the AgencyTable Servlet ..............................................................548
Summary ............................................................................................................552
Q&A ....................................................................................................................553
Exercises ............................................................................................................553
DAY 13 JavaServer Pages
555
What is a JSP? ....................................................................................................556
Separating Roles ............................................................................................557
Translation and Execution ............................................................................557
JSP Syntax and Structure ....................................................................................557
JSP Elements ................................................................................................558
First JSP example ..............................................................................................560
JSP Problems ................................................................................................563
JSP Lifecycle ......................................................................................................563
Detecting and Correcting JSP Errors ............................................................565
JSP Lifecycle Methods ..................................................................................569
00 0672323842 FM
3/20/02
9:31 AM
Page xv
Contents
xv
JSP Directives ....................................................................................................570
The include Directive ..................................................................................570
The page Directive ........................................................................................571
Accessing HTTP Servlet Variables ....................................................................575
Using HTTP Request Parameters ......................................................................576
Simplifying JSP pages with JavaBeans ..............................................................577
What Is a JavaBean? ......................................................................................578
Defining a JavaBean ......................................................................................579
Getting Bean Properties ................................................................................579
Setting Bean Properties ................................................................................580
Initializing Beans ..........................................................................................581
Using a Bean with the Agency Case Study ..................................................581
Adding a Web Interface to the Agency Case Study ..........................................585
Structure and Navigation ..............................................................................585
Look and Feel ................................................................................................588
Error Page Definition ....................................................................................595
Deploying the Case Study JSPs ....................................................................597
Comparing JSP with Servlets ............................................................................600
Summary ............................................................................................................601
Q&A ....................................................................................................................601
Exercise ..............................................................................................................602
DAY 14 JSP Tag Libraries
603
The Role of Tag Libraries ..................................................................................604
Developing a Simple Custom Tag ......................................................................605
Using a Simple Tag ......................................................................................605
The Tag Library Descriptor (TLD) ..............................................................606
Custom Java Tags ..........................................................................................608
The doStartTag() Method ............................................................................610
The “Hello World” Custom Tag ..................................................................611
Deploying a Tag Library Web Application ..................................................612
Defining the TLD Location ..........................................................................614
Using Simple Tags ........................................................................................614
Tags with Attributes ............................................................................................615
Tags that Define Script Variables ........................................................................618
Iterative Tags ......................................................................................................622
Co-operating Tags ..............................................................................................626
Using Shared Scripting Variables ..................................................................626
Hierarchical Tag Structures ..........................................................................627
Defining Tag Extra Info Objects ........................................................................634
Validating Attributes ......................................................................................635
Defining Scripting Variables ........................................................................637
00 0672323842 FM
xvi
3/20/02
9:31 AM
Page xvi
Sams Teach Yourself J2EE in 21 Days
Processing Tag Bodies ........................................................................................637
JavaServer Pages Standard Tag Library (JSPTL) ..............................................640
Using the JSPTL with the J2EE RI ..............................................................641
Using the JSPTL forEach Tag ....................................................................643
Other JSPTL Tags ........................................................................................645
JSPTL Scripting Language ............................................................................645
Other Jakarta Tag Libraries ..........................................................................646
Summary ............................................................................................................647
Q&A ....................................................................................................................647
Exercise ..............................................................................................................648
WEEK 3 Integrating J2EE into the Enterprise
DAY 15 Security
651
653
Security Overview ..............................................................................................654
Security Terminology ....................................................................................654
Common Security Technology ..........................................................................656
Symmetric Encryption ..................................................................................656
Asymmetric Encryption ................................................................................658
SSL and HTTPS ............................................................................................659
Checksums and Digests ................................................................................660
Digital Certificates ........................................................................................660
Security in J2EE ................................................................................................661
J2EE Security Terminology ..........................................................................661
Working with J2EE RI Security ....................................................................663
Security and EJBs ..............................................................................................666
Defining EJB Security ..................................................................................666
Defining Roles ..............................................................................................666
Defining the Security Identity ......................................................................668
Defining Method Permissions ......................................................................670
Mapping Principals to Roles ........................................................................674
Using Roles as the Security Identity ............................................................676
Security in Web Applications and Components ................................................682
Web Authentication ......................................................................................683
Configuring J2EE RI Basic Authentication ..................................................684
Declarative Web Authorization ....................................................................685
Programmatic Web Authorization ................................................................691
Adding Programmatic Web Security to the Case Study ..............................692
Using Secure Web Authentication Schemes ................................................694
Security and JNDI ............................................................................................695
Simple LDAP Authentication ........................................................................696
SASL Authentication ....................................................................................696
00 0672323842 FM
3/20/02
9:31 AM
Page xvii
Contents
xvii
Summary ............................................................................................................698
Q&A ....................................................................................................................699
Exercises ............................................................................................................699
DAY 16 Integrating XML with J2EE
701
The Drive to Platform-Independent Data Exchange ..........................................702
Benefits and Characteristics of XML ............................................................703
Origins of XML ..................................................................................................703
Structure and Syntax of XML ............................................................................704
HTML and XML ..........................................................................................705
Structure of an XML Document ..................................................................705
Declarations ..................................................................................................706
Elements ........................................................................................................706
Well-Formed XML Documents ....................................................................708
Attributes ......................................................................................................708
Comments ......................................................................................................709
Creating Valid XML ..........................................................................................710
Document Type Definitions ..........................................................................710
Namespaces ..................................................................................................714
Enforcing Document Structure with an XML Schema ................................715
How XML Is Used in J2EE ..........................................................................718
Parsing XML ......................................................................................................718
The JAXP Packages ..........................................................................................720
Parsing XML using SAX ....................................................................................720
Document Object Model (DOM) Parser ..........................................................725
Modifying a DOM Tree ......................................................................................731
Java Architecture for XML Binding ..................................................................732
Differences Between JAXP and JAXB ........................................................733
Extending the Agency Case Study ....................................................................734
Step 1—Change Session Beans ....................................................................735
Step 2—Amend the MessageSender Helper Class ........................................736
Step 3—Amend the ApplicantMatch Message-Driven Bean ......................737
Summary ............................................................................................................739
Q&A ....................................................................................................................739
Exercises ............................................................................................................740
DAY 17 Transforming XML Documents
741
Presenting XML to Clients ................................................................................742
Presenting XML to Browsers ........................................................................743
Extensible Stylesheet Language (XSL) ........................................................744
XSL-FO XSL Formatting Objects ................................................................744
00 0672323842 FM
xviii
3/20/02
9:31 AM
Page xviii
Sams Teach Yourself J2EE in 21 Days
Extensible Stylesheet Transformations (XSLT) ................................................745
Applying Stylesheets ....................................................................................746
Storing Transformed Documents on the Server ............................................746
Presenting XML Documents and Stylesheets to the Client ..........................747
Transforming the XML Document on the Server ........................................747
Using XALAN with J2EE ..................................................................................748
Transforming XML Documents with XALAN ............................................749
Using XALAN from the Command Line ....................................................750
Using XSLT in Java Applications ....................................................................751
XSLT Stylesheets ................................................................................................755
Template Rules ..............................................................................................756
Text Representation of XML Elements ......................................................761
Using XPath with XSLT ................................................................................762
Default Stylesheet Rules ..............................................................................764
Processing Attributes ....................................................................................765
Using Stylesheet Elements ................................................................................767
Processing Whitespace and Text ..................................................................767
Adding Comments ........................................................................................769
Attribute Values ............................................................................................770
Creating and Copying Elements ....................................................................771
Attributes and Attribute Sets ........................................................................774
Additional XSL Elements ............................................................................777
XSLT Compilers ................................................................................................780
Summary ............................................................................................................781
Q&A ....................................................................................................................782
Exercises ............................................................................................................782
DAY 18 Patterns
787
J2EE Patterns ......................................................................................................788
What Are Patterns? ........................................................................................788
Why Use Patterns? ........................................................................................790
Types of Patterns ..........................................................................................790
J2EE Patterns ................................................................................................791
Pattern Catalogs ............................................................................................792
Applying J2EE-Specific Patterns ......................................................................792
Applying Patterns in a Context ....................................................................793
Generic Patterns ............................................................................................794
J2EE Presentation-Tier Patterns ....................................................................795
J2EE Business-Tier Patterns ..........................................................................795
J2EE Integration-Tier Patterns ......................................................................796
Patterns Within J2EE ....................................................................................797
00 0672323842 FM
3/20/02
9:31 AM
Page xix
Contents
xix
Patterns in Context ..............................................................................................797
Analysing the Case Study ............................................................................797
Session Facades and Entity EJBs ..................................................................798
Data Exchange and Value Objects ................................................................800
Data Access Without Entity EJBs ................................................................804
Messages and Asynchronous Activation ......................................................811
Composing an Entity ....................................................................................812
Composing a JSP ..........................................................................................813
JSPs and Separation of Concerns ..................................................................817
Client-Side Proxies and Delegates ................................................................820
Locating Services ..........................................................................................821
Any Other Business ......................................................................................822
Refactoring the Case Study ..........................................................................822
Directions for J2EE Patterns ..............................................................................823
Summary ............................................................................................................824
Q & A ..................................................................................................................824
Exercises ............................................................................................................825
DAY 19 Integrating with External Resources
827
Reviewing External Resources and Legacy Systems ........................................828
Introducing Connector Architecture ..................................................................829
Overview of the Architecture ........................................................................829
Roles and Responsibilities ............................................................................830
Using the Common Client Interface ..................................................................834
Interacting with an EIS ..................................................................................834
Installing a Resource Adapter ......................................................................835
Creating a First CCI Application ..................................................................836
Managing Transactions and Exploring Records ..........................................843
Introducing Other Connectivity Technologies ....................................................848
Introducing CORBA ..........................................................................................849
Introducing Java IDL ..........................................................................................851
Using RMI over IIOP ........................................................................................851
RMI over JRMP Example ............................................................................852
RMI over IIOP Example ..............................................................................857
Introducing JNI ..................................................................................................860
Evaluation of Integration Technologies ..............................................................865
Summary ............................................................................................................865
Q&A ....................................................................................................................866
Exercises ............................................................................................................867
DAY 20 Using RPC-Style Web Services with J2EE
869
Web Service Overview ......................................................................................870
What Is a Web Service? ................................................................................870
Why Use Web Services? ..............................................................................872
00 0672323842 FM
xx
3/20/02
9:31 AM
Page xx
Sams Teach Yourself J2EE in 21 Days
Web Service Technologies and Protocols ....................................................873
Web Service Architecture ..............................................................................873
Web Services for J2EE ......................................................................................875
J2EE Web Service Architecture ....................................................................875
Tools and Technologies ................................................................................876
Integrating Web Services with Existing J2EE Components ........................878
Using an RPC-style SOAP-Based Web Service ................................................879
RPC-Oriented Web Services ........................................................................880
Setting up Axis under Tomcat 4.0 ................................................................881
Service Description Information ..................................................................883
Anatomy of a WSDL Document ..................................................................883
Creating a Java Proxy from WSDL ..............................................................885
Calling the Web Service Through SOAP ......................................................889
A Half-Way House ........................................................................................891
Debugging a SOAP Interaction ....................................................................892
Implementing an RPC-Style SOAP-Based Web Service ..................................894
Wrapping up a Java class as a Web Service ..................................................894
A Client for Your Web Service ......................................................................898
Starting from WSDL ....................................................................................900
Using Axis JWS files ....................................................................................903
Session Context and Web Services ..............................................................905
Wrapping Existing J2EE Functionality as Web Services ............................909
Parameter Types and Type Mapping ..................................................................911
Mapping Between Java and SOAP/WSDL Types ........................................911
Mapping Complex Types with Serializers ....................................................912
Going Further with Complex Type Mapping ................................................919
Summary ............................................................................................................919
Q&A ....................................................................................................................920
Exercises ............................................................................................................921
DAY 21 Web Service Registries and Message-Style Web Services
923
Registries for Web Services ................................................................................924
What is a Web Service Registry? ..................................................................924
Why Do I Need One? ....................................................................................924
How Do They Work? ....................................................................................925
Types of Registry ..........................................................................................925
ebXML Registry and Repository ..................................................................926
UDDI Overview ............................................................................................928
Accessing Information in a UDDI Registry ......................................................929
Manipulating Service Information using UDDI4J ........................................929
Manipulating Service Information Using the IBM WSTK Client API ........932
Retrieving and Using Service Information ..................................................933
00 0672323842 FM
3/20/02
9:31 AM
Page xxi
Contents
xxi
Using JAXR for Registry Access ......................................................................934
A Generic Approach ......................................................................................934
Using JAXR to Store and Retrieve Service Information ..............................936
Using a Message-Based SOAP Interface ..........................................................937
Message-Style Versus RPC-style ..................................................................937
Creating a Client ............................................................................................938
Creating a Service ........................................................................................939
Sending and Receiving SOAP Messages with JAXM ......................................939
JAXM and J2EE ............................................................................................941
Configuring JAXM ........................................................................................941
Sending Basic SOAP Messages ....................................................................942
Running the Simple Client ............................................................................947
Populating the Message ................................................................................947
Headers and Attachments ..............................................................................951
Receiving a SOAP Message Using JAXM ..................................................952
Using a JAXM Profile ..................................................................................955
Sending a Message Using a JAXM Profile ..................................................957
Receiving a Message Using a JAXM Profile ................................................959
Summary ............................................................................................................962
Q&A ....................................................................................................................962
Exercises ............................................................................................................963
Appendix A An Introduction to UML
965
Introducing the UML ..........................................................................................965
Use Case Diagrams ............................................................................................967
Class Diagrams ..................................................................................................969
Associations ..................................................................................................969
Attributes ......................................................................................................970
Operations ......................................................................................................971
Generalization ................................................................................................972
Constraints ....................................................................................................973
Sequence Diagrams ............................................................................................973
Appendix B SQL Reference
977
Commonly Used SQL Statements (SQL99) ......................................................978
ALTER TABLE ..................................................................................................978
CREATE TABLE..................................................................................................979
CREATE VIEW ..................................................................................................979
DELETE ............................................................................................................980
DROP TABLE ....................................................................................................980
DROP VIEW ......................................................................................................980
INSERT ............................................................................................................980
00 0672323842 FM
3/20/02
9:31 AM
Page xxii
............................................................................................................981
............................................................................................................983
Commonly Used SQL Clauses ..........................................................................983
FROM ................................................................................................................983
WHERE ..............................................................................................................983
GROUP BY ........................................................................................................984
HAVING ............................................................................................................984
ORDER BY ........................................................................................................984
SELECT
UPDATE
Appendix C An Overview of XML
987
What Is XML? ....................................................................................................988
Elements ........................................................................................................988
Declarations ..................................................................................................989
Comments ......................................................................................................990
Special Characters ........................................................................................990
Namespaces ..................................................................................................991
Enforcing XML Document Structure ................................................................991
Document Type Definition (DTD) ................................................................992
XML Schema ................................................................................................995
Where to Find More Information ......................................................................997
Appendix D The Java Community Process
999
Introducing the JCP ............................................................................................999
Getting Involved ..............................................................................................1000
JCP Members ..............................................................................................1000
Expert Groups ..............................................................................................1000
The Public ....................................................................................................1000
Process Management Office (PMO) ..........................................................1001
Executive Committees ................................................................................1001
Understanding the JSR Process ........................................................................1001
Taking the Next Step ........................................................................................1002
Glossary
1003
Index
1025
00 0672323842 FM
3/20/02
9:31 AM
Page xxiii
About the Authors
The authors of this book work for Content Master Ltd., a technical authoring company in
the United Kingdom specializing in the production of training and educational materials.
For more information on Content Master, please see its Web site at
www.contentmaster.com.
Martin Bond, B.Sc. M.Sc. C.Eng, M.B.C.S., was born near Manchester England in
1958. Martin left a budding academic career to develop parallel processing compilers for
Inmos. Martin has designed and developed systems using C++, Java, and JavaScript and
has developed training courses on Unix programming, Solaris security, Java programming, and XML. Martin has an honors degree and a masters degree in computer science
from Aberystwyth, Wales, and is a European chartered engineer. Martin currently works
as an IT trainer and consultant based in Cornwall, England.
Dan Haywood has been working on large and small software development projects for
more than 12 years. These days, he fills his days with consulting, training and technical
writing, specializing in OO design, Java and J2EE, Sybase technical consulting, and data
modeling. Previously, Dan worked at Sybase Professional Services, performing a variety
of roles, mostly in the financial industry, including architect, performance specialist, and
project manager. Dan started his IT career at (what was then) Andersen Consulting,
working as a developer on large-scale projects in government and in utilities. Dan is married and has a baby daughter.
Debbie Law B.Sc., was born in Romsey, England in 1959. Debbie started on compiler
development for parallel processing systems, later working on the design and development of client server applications. As a technical manager for Siemens, she was one of a
small group of select staff on an intensive learning program studying worldwide business
practices, including several weeks at MIT and Harvard. Debbie has an honors degree in
computer science from Southampton, England and currently works as an IT consultant
based in Cornwall, England.
Andy Longshaw is a consultant, writer, and educator specializing in J2EE, XML, and
Web-based technologies and components, particularly the design and architecture decisions required to use these technologies successfully. Andy has been explaining technology for most of the last decade as a trainer and in conference sessions. A wild rumor suggests that some people have managed to stay awake in these sessions. Despite being well
educated and otherwise fairly normal, Andy still subjects himself and his family to “trial
by unpredictability” by watching Manchester City FC far more often than is healthy.
00 0672323842 FM
3/20/02
9:31 AM
Page xxiv
Peter Roxburgh graduated with a first class degree with honors in business, and has
since followed a diverse career path. From his home in the medieval walled town of
Conwy, North Wales, he authors a wide-variety of training courses, and books including
Building .NET Applications for Mobile Devices (Microsoft Press, 2002). He has also
written and contributed to a number of journals and Web sites on cutting-edge technologies.
Peter spends his spare time playing guitar and bouldering on nearby sea cliffs and mountain crags. When he is not strumming or risking life and limb, he enjoys spending relaxing and quality time with his daughter, Chloe.
00 0672323842 FM
3/20/02
9:31 AM
Page xxv
Dedication
To Sarah, for encouragement, advice, and regular supplies of flapjacks; and to Adam and Josh, for providing me with a life that doesn’t revolve around computers. —AL
To Sue: Thank you for all these happy years. —Love, Dan.
Acknowledgments
The authors would like to thank the various project managers and editors involved in this
book, without whom it would never have seen the light of day. Special thanks go to
Suzanne Carlino at Content Master and Todd Green, Michael Watson, Christy Franklin,
and the editing team at SAMS. We would also like to acknowledge the work of Alex
Ferris and John Sharp in the initial phases of this project.
00 0672323842 FM
3/20/02
9:31 AM
Page xxvi
Tell Us What You Think!
As the reader of this book, you are our most important critic and commentator. We value
your opinion and want to know what we’re doing right, what we could do better, what
areas you’d like to see us publish in, and any other words of wisdom you’re willing to
pass our way.
As an executive editor for Sams Publishing, I welcome your comments. You can fax,
e-mail, or write me directly to let me know what you did or didn’t like about this book—
as well as what we can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this
book, and that due to the high volume of mail I receive, I might not be able to reply to
every message.
When you write, please be sure to include this book’s title and author as well as your
name and phone or fax number. I will carefully review your comments and share them
with the author and editors who worked on the book.
Fax:
317-581-4770
E-mail:
feedback@samspublishing.com
Mail:
Michael Stephens
Executive Editor
Sams Publishing
201 West 103rd Street
Indianapolis, IN 46290 USA
01 0672323842 Intro
3/20/02
9:23 AM
Page 1
Introduction
The world has come a long way since Duke first started tumbling in early versions of
Netscape Navigator. Java has outgrown its humble origins as a cool way of providing
interactivity on Web pages and has found a new role as a major, server-side development
platform. The actual Java language has changed little in the intervening years, but an
enterprise-quality infrastructure has risen up around it. This infrastructure, Java 2
Enterprise Edition or J2EE for short, allows Java developers to create sophisticated and
powerful enterprise applications that provide mission-critical functionality for many
thousands of users.
Unlike competing platforms, such as Microsoft .NET, J2EE is a specification rather than
a product. The capabilities and functionality of each release of J2EE is agreed on through
the Java Community Process (JCP). The platform is then implemented by application
server vendors and producers, such as BEA, IBM, iPlanet, ATG, SilverStream, and
JBOSS. This means that J2EE developers have a choice of product vendors from whom
to select, based on quality, support, or ease of use. The ability to submit technologies
through the JCP, and the two-way flow that exists between the main Java vendors and the
open-source community, ensures that a constant stream of new ideas helps to move J2EE
forward.
This book intends to take you on a journey through the J2EE landscape, from the simplest components through design considerations and on to the latest Web Services. There
is a lot to learn in three weeks—but this should provide the essential grounding you need
to use the J2EE platform effectively. If you need to create robust enterprise applications
and Java is your tool of choice, read on.
How This Book is Organized
Sams Teach Yourself J2EE in 21 Days covers version 1.3 of the J2EE platform. It is organized as three separate weeks that guide you through the different functionality provided
by J2EE.
The first week gives you a broad grounding in J2EE before moving on to investigate
Enterprise JavaBeans (EJBs) in detail:
• Day 1, “The Challenge of N-Tier Development,” defines the landscape in which
J2EE applications operate and provides the architectural concepts with which you
need to become familiar to create J2EE applications.
01 0672323842 Intro
2
3/20/02
9:23 AM
Page 2
Sams Teach Yourself J2EE in 21 Days
• Day 2, “The J2EE Platform and Roles,” takes you on a whistle-stop tour of the
J2EE platform, the major technologies, the types of component from which J2EE
applications are assembled, and the container with which they interact. You also
install the J2EE platform and start to look at the case study used throughout the
book.
• On Day 3, “Naming and Directory Services,” you start using your first J2EE API,
the Java Naming and Directory Interface (JNDI), to store, retrieve, and manipulate
information that can be accessed by all J2EE components.
• Day 4, “Introduction to EJBs,” introduces Enterprise JavaBeans (EJB)—the core
technology of the J2EE platform. You will examine the role of EJBs and how they
work. You will then deploy an example EJB and create a simple client application
for it.
• On Day 5, “Session EJBs,” you will explore Session EJBs in more depth. This
includes the creation of both stateful and stateless Session EJBs.
• Day 6, “Entity EJBs,” moves on to Entity EJBs and examines their role and lifecycle. Particular attention is paid to how state is stored and retrieved using BeanManaged Persistence (BMP).
• On Day 7, “CMP and EJB QL,” the discussion of Entity EJBs expands to cover
entities that use Container-Managed Persistence (CMP) to store and retrieve their
state. This includes an exploration of the EJB Query Language and ContainerManaged Relationships that were introduced in J2EE 1.3.
The second week moves beyond EJBs to look at asynchronous interaction and the development of Web-based components:
• On Day 8, “Transactions and Persistence,” you will delve deeper into the use of
transactions in the J2EE platform—what they can achieve and how your components can take advantage of them. Some alternative persistence mechanisms are
also explored.
• Day 9, “Java Message Service,” looks at asynchronous messaging with the Java
Message Service (JMS) using message queues and topics. You will apply JMS to
implement a producer and consumer of asynchronous messages.
• Day 10, “Message-Driven Beans,” builds on the coverage of JMS to associate message queues with Message-driven EJBs. You will create an EJB whose functionality is triggered on receipt of an asynchronous message.
• On Day 11, “JavaMail,” another asynchronous communication mechanism is
examined—namely e-mail. You will learn how to send and retrieve e-mail under
J2EE and how this can be applied to transport data in a J2EE application.
01 0672323842 Intro
3/20/02
9:23 AM
Page 3
Introduction
• Day 12, “Servlets,” is the first of three Web-oriented days that explore the creation
of Web-oriented J2EE applications. This starts by creating servlets to take advantage of the EJB-based services you built earlier. You will look at the servlet lifecycle and central issues, such as session tracking and state management.
• Day 13, “JavaServer Pages,” looks at how JavaServer Pages (JSP) can help to integrate Java and J2EE functionality with HTML content. It examines the role of JSPs
and how JavaBeans can be used to encapsulate Java functionality in JSPs.
• On Day 14, “JSP Tag Libraries,” you will use custom JSP tag libraries to encapsulate Java functionality to improve the maintainability of the JSP pages.
The third week explores essential aspects of enterprise applications, such as security and
integration, before moving on to application design and ending with a look at the Web
Service functionality that will form the future of J2EE:
• Day 15, “Security,” begins week 3 by applying security to your J2EE application.
You will weigh up the benefits of declarative and programmatic security and how
they can be applied within your application.
• On Day 16, “Integrating XML with J2EE,” you will examine the role of XML in
J2EE applications. You will create J2EE components that produce and consume
XML documents and process data using the Java APIs for XML Processing
(JAXP).
• Day 17, “Transforming XML Documents,” focuses on the transformation of XML
documents into other formats, including other dialects of XML, primarily using the
XSLT transformation language. Again, JAXP allows you to do this programmatically from within J2EE components.
• On Day 18, “Patterns,” you will take some time to consider the bigger picture and
examine design issues for J2EE applications. The specific focus will be on common patterns that have been found as people have applied J2EE technologies in
live applications. You will use this knowledge to improve parts of the case study
design.
• Day 19, “Integrating with External Resources,” explores the various technologies
that can be used to integrate J2EE applications with non-J2EE components and services. These mechanisms include the Java Connector Architecture, CORBA, RMIIIOP, and the Java Native Interface.
• Day 20, “Using RPC-Style Web Services with J2EE,” looks ahead to the use of
J2EE components as Web Services. You will use common Web Service technologies (such as SOAP and WSDL) to expose Java functionality as Web Services, and
look at how the Java API for XML-Based RPC (JAX-RPC) addresses this.
3
01 0672323842 Intro
4
3/20/02
9:23 AM
Page 4
Sams Teach Yourself J2EE in 21 Days
• Day 21, “Web Service Registries and Message-Style Web Services,” concludes the
examination of J2EE-based Web Services by examining the role of XML-based
registries and how the Java API for XML Registries (JAXR) enables access to this
information. You will also create a message-oriented producer and consumer of
Web Services using the Java API for XML Messaging (JAXM).
About This Book
This book is a practical, down-to-earth guide for intermediate Java developers. It is not
intended to be a reference book, with lists of API calls or extensive discussion of the
inner workings of the technologies. Rather, it provides you with a grounding in applying
the essential J2EE technologies and leads you through the essential steps required to get
a program or component written, packaged, and deployed on the J2EE platform. By the
time you finish Sams Teach Yourself J2EE in 21 Days, you should have the confidence to
create or maintain code that uses any of the major J2EE APIs.
Who Should Read This Book?
This book in intended for experienced Java developers who have been involved with Java
development for at least 3–6 months. You should be confident writing Java code and
familiar with the commonly used Java 2 Standard Edition APIs, such as string handling,
JDBC, collections, iterators, and so on.
In addition to a firm grasp of Java, the following knowledge will speed your progress
through the book:
• An understanding of how the Web operates, such as the use of a Web browser to
retrieve pages of HTML from Web Servers.
• Familiarity with XML syntax to the level of reading small extracts of XML containing elements, attributes, and namespaces.
• An understanding of relational databases and how data is structured in tables. A
familiarity with basic SQL to the level of understanding simple queries, inserts,
updates, and joins.
• Familiarity with distributed systems, such as n-tier development, client-server programming, and remote procedure calls.
If you are not familiar with one or more of these topics, don’t panic! There are appendixes
on the CD-ROM that provide introductory material on XML and SQL. The essential concepts of distributed systems and Web-based development are covered in the main body of
the book as required.
01 0672323842 Intro
3/20/02
9:24 AM
Page 5
Introduction
How This Book is Structured
This book is intended to be read and absorbed over the course of three weeks. During
each week, you read seven chapters that present concepts related to J2EE and the creation of enterprise applications in Java. Care has been taken to try to ensure that concepts
and technologies are introduced in an appropriate order, so it is best to read the chapters
sequentially if possible.
At the end of each lesson are a set of questions asked about the subject covered that day.
Answers to these questions are provided by the authors. There are also exercises for you
to test your newly found skills by creating some related application or service.
The exercises in the book are largely based around a case study that is described in detail
at the end of Day 2. The files for the case study and worked solutions to the exercises
can be found on the CD-ROM that accompanies this book. The idea of the case study is
that it will help you apply J2EE technologies and techniques in a consistent context and
as part of a working application. This should provide you with a deeper understanding of
the technology involved and how to apply it than is possible working with standalone
examples.
Typographic Conventions
Note
A Note presents interesting, sometimes technical, pieces of information
related to the surrounding discussion.
Tip
A Tip offers advice or suggests an easier way of doing something.
Caution
A Caution advises you of potential problems and helps you avoid causing
serious damage.
Text that you type, text that should appear on your screen, and the names of Java classes
or methods are presented in monospace type.
5
01 0672323842 Intro
3/20/02
9:24 AM
Page 6
02 0672323842 Week1
3/20/02
9:35 AM
Page 7
WEEK 1
Introducing J2EE
and EJBs
1 The Challenge of N-Tier
Development
1
2
3
2 J2EE Platform and Roles
3 Naming and Directory Services
4 Introduction to EJBs
4
5 Session EJBs
6 Entity EJBs
7 CMP and EJB QL
5
6
7
02 0672323842 Week1
3/20/02
9:35 AM
Page 8
03 0672323842 CH01
3/20/02
9:26 AM
Page 9
WEEK 1
DAY
1
The Challenge of N-Tier
Development
The current trend in enterprise program development is to provide n-tier frameworks aimed at delivering applications that are secure, scalable, and available.
To this end, Sun Microsystems introduced Java 2 Enterprise Edition (J2EE),
and Microsoft Corporation ventured the .NET framework to help developers
build applications that are Web-friendly and frequently used to deliver
e-commerce solutions. There are a myriad of application servers available to
house enterprise applications, and many service providers are writing modular
tools to plug in and extend the rich functionality. The clients that are taking
advantage of this distributed architecture can be as simple as a Web browser (a
so-called thin client).
This is the overarching vision and the state of the art. But, how did we get
here?
To understand this landscape, this chapter investigates the principles of multiple
tiers, component environments, and standards that underlie the frameworks.
One of the objectives will be to give you a clear understanding of concepts and
03 0672323842 CH01
3/20/02
9:26 AM
Page 10
10
Day 1
terminology used when discussing such frameworks. Such terminology can frequently be
confusing and inconsistently used. As a start along this road, please note that for the purposes of the following discussions, a tier refers to a physical separation (a different
machine), and a layer refers to a logical layer in software terms, such that multiple layers
can be on the same machine.
Monolithic Development
In the days of the mainframe or the standalone personal computer, when an application
was housed on a single machine, it was common to find monolithic applications containing all the functionality of the application in one large, frequently unmaintainable piece
of software (sometimes referred to as spaghetti code). All user input, verification, business logic, and data access could be found together. This suited the world of the mainframe and corporate data center because everything was controlled and the systems
themselves tended to evolve slowly. However, as the world has speeded up over the last
two decades, the high levels of maintenance required to keep up with changing business
needs using such an application would mean that recompilation would be almost a daily
event.
Note
Even today, if you need a very simple application where, for example, the
client application accesses and updates information on a database, locally
you need only one tier. However, as you will see, you will still probably want
to use components and or layers to control its complexity.
Figure 1.1 shows how this application may look running on a single machine.
FIGURE 1.1
Monolithic code
scenario.
Monolithic Code
Presentation
Logic
Business
Logic
Data
Access Logic
Database
Consequences of Monolithic Applications
If you are writing a simple utility that does not use network connectivity, the previous scenario might suffice. However, any changes required to any part of the functionality may
03 0672323842 CH01
3/20/02
9:26 AM
Page 11
The Challenge of N-Tier Development
potentially affect other parts. Because the Presentation, Business, and Data Access logic
are located within the same piece of application code, recompilation of many parts of the
code may be necessary, increasing the overhead of adding or changing functionality.
Worse still, changes in part of the code may introduce unintentional bugs in other, seemingly unrelated, parts.
Of course, updating the application involves only one machine, but the rollout of new
versions of the software gets more complicated as more users install and use the application.
The Move into the Second Tier
The move towards 2-tier systems was born from the desire to share data between multiple applications installed on different machines. To do this, a separate database server
machine was required. Figure 1.2 shows how this is achieved. The application now consists of presentation and business logic. Data is accessed by connecting to a database on
another machine. Any changes to the Data Access logic should not affect the
Presentation or Business logic in the application.
As indicated by Figure 1.2, splitting out Data Access Logic into a second tier keeps the
data access independent and can deliver a certain amount of scalability and flexibility
within the system.
FIGURE 1.2
2 Tier
2-tier scenario.
Presentation
Logic
Business
Logic
Data
Access Logic
Database
The advantage of having the Data Access Logic split into a separate physical environment means that not only can data be shared, but any changes to the data access logic are
localized in that second tier. In fact, the whole of the second tier could be replaced with a
different database and different code as long as the interface between the two tiers
remained the same.
This provides an alternative way of looking at the program logic. Each part of the logic
from the monolithic system could be regarded as a separate layer.
11
1
03 0672323842 CH01
12
3/20/02
9:26 AM
Page 12
Day 1
The logical division into layers of functionality can be based on the different responsibilities of parts of the code, namely,
• Presentation Logic—This dictates how the user interacts with the application and
how information is presented.
• Business Logic—This houses the core of the application, namely the rules governing the business process (or any other functionality) embedded in the application.
• Data Access Logic—This governs the connection to any datasources used by the
application (typically databases) and the provision of data from those datasources
to the business logic.
So, we have two tiers in Figure 1.2 with two logical layers. The Presentation and
Business Logic layers are still lumped together as one piece of potentially monolithic
code.
Consequences of the 2-Tier Design
One of the central problems faced by application developers using the type of architecture shown in Figure 1.2 was that the client is still full of business code and it still needs
to know details about the location of its data sources. Because there is such a concentration of functionality on the client, this type of client is generally termed a thick client.
Thick clients usually need to be updated whenever the application changes.
Because the users of a thick client application have much of the application code
installed on their local systems, there is a need to install fresh copies of the updated
application when changes are made. This presents a serious manageability issue in terms
of roll out and version control. Also, it is not always practical to use a thick client,
because the application user may not want to install code on his or her machine to use a
particular application. Similarly, the application provider may not want to provide code
containing its business logic to relatively unknown third parties, even if it is precompiled.
Another issue with the use of thick clients relates to data access. The need to provide
access to the back-end data for all clients of the application severely limits the reach and
scalability of the application.
In addition to these inherent problems, many applications written with tools aimed at the
two-tier environment still had all of their code in a single executable module. This
increased maintenance headaches because there was a need to update the program design
and implementation if any changes are required to any part of the system. With the
advent of the Internet, there was a movement to separate Business logic from the user
interface. Internet users, or more precisely Web users, need to access applications
03 0672323842 CH01
3/20/02
9:26 AM
Page 13
The Challenge of N-Tier Development
without installing new code on their machines. In fact, they want to be able to use the
same client application—a Web browser—to access all of the different applications they
encounter on the Web. Because the application logic associated with a thick client is no
longer resident on the user’s machine, this type of client is known as a thin client. The
implication is that all of the “bulk” of the application has been moved into another tier.
When a Web browser is used as a thin client, the application code will be run on the Web
servers with which the browser communicates (or on other machines with which the Web
servers communicate). The presentation tier logic for such an application must generate
Hypertext Markup Language (HTML) rather than manipulate graphical elements on a
GUI screen.
All of this has a serious implication for 2-tier systems. If a 2-tier system is to be adapted
for use on the Internet, the thick client part that contains the business logic and the presentation logic must be re-written to run on a Web server. This will then mean that there
are two copies of the business logic—one housed in the original thick client and the
other housed in the Web-based version of the application. This is a nightmare in maintenance terms because any changes or updates must be made in both places. More decoupling is required to improve the manageability and maintainability of the application.
The decoupling of application logic by introducing additional tiers, as started with the
two-tier system shown in Figure 1.2, can be continued with the separation of the
Business and Presentation Logic. By housing the separated Business Logic in another
tier, the thick client suddenly becomes thinner, as Figure 1.3 shows.
FIGURE 1.3
3 Tier
3-tier scenario.
Presentation
Logic
Business
Logic
Data
Access Logic
Database
The Presentation Logic is now separated into its own layer in its own tier. This means
that different types of Presentation Logic, such as HTML-based and GUI-based user
interface code, can all access the same Business Logic on the middle tier.
This 3-tier model has become the de-facto architecture for Web-based business systems.
The separation into layers makes systems more flexible so that parts can be changed
independently. An example of this would be creating a presentation layer specifically
13
1
03 0672323842 CH01
3/20/02
9:26 AM
Page 14
14
Day 1
targeted at mobile devices. Given the separation of business and presentation functionality, this should not require any changes to the business of Data Access logic. The separation into separate physical tiers provides the opportunity to inject enhanced scalability
and availability by replicating machines and software at the different tiers.
With the logic now separated into layers, it is far easier to write code that is tailored to
its particular task. For example, because the Presentation logic is now housed in its own
physical and logical layer, such code can be written by a developer who is skilled in this
particular area. Developers who are skilled in the use of Java Web components, such as
servlets (see Day 12, “Servlets”) and Java Server Pages (JSPs) (see Day 13, “JavaServer
Pages”) can write the code for this layer. These developers do not need to know about the
technologies used in the business or data access code.
Complexity Simplified by Modularity
When designing a system, certain concepts will naturally sit together. By placing these
into shared modules, a certain amount of separation can be achieved that is independent
of the layering discussed so far. Functionality can be split into classes, and these classes
can be grouped in packages or components. By reducing the dependencies between the
classes and packages, this functionality can be used by different parts of the application.
By defining and maintaining interfaces between classes and packages, the actual implementation of a class can be replaced without requiring a change to other classes that
depend on it. The Unified Modeling Language (UML) diagram in Figure 1.4 shows this
type of decomposition.
FIGURE 1.4
Modularity.
Component
Package
Package
Class
Class
State
Behaviour
Class
State
Behaviour
Class
State
Behaviour
State
Behaviour
Class
State
Behaviour
03 0672323842 CH01
3/20/02
9:26 AM
Page 15
The Challenge of N-Tier Development
Object-oriented (OO) modeling promotes modularity to a large extent. Objects encapsulate their data or state and offer functionality through their interfaces. If designed correctly, the dependencies between different objects can be minimized. This reduction in
dependency means that the objects are loosely coupled. Loosely coupled systems tend to
be easier to maintain and evolve.
Object-oriented programming tried to improve maintainability with encapsulation and to
aid system design with a definition of specific classes for specific roles, providing coherent groups of functionality. This significantly improved the previously poorly designed
monolithic code and made things more maintainable and flexible. However, it was language-specific (Java, C++, and Smalltalk) and so did not make deployment or integration
easier.
Although it is not the whole solution, you have some useful tools for modularizing your
applications in Java:
• A Java class is a way of adding modularity by housing all state and behavior
belonging to an entity into one part of the design.
• A Java package is another way of using modularity to house all classes and interfaces that belong together to perform a specific set of functions.
What you then need is a way of going beyond simple objects to provide more coarsegrained packages of functionality that can be glued together to create custom applications. To be correctly glued, these packages must conform to certain rules that are
defined by a framework. This leads us to components.
Component Technology
A component is a unit of functionality that can be used within a particular framework.
Component frameworks have evolved to provide support for simplified application development. When using a component framework, a container provides the components with
certain standard services, such as communication and persistence. Because standard
mechanisms are used for component definition and inter-component communication, it
becomes possible to write tools that examine components and display their information
to an application writer. A developer can then use the tool to drag and drop these components into his or her application. This can be seen in the typical GUI interface builder
environments, such as Visual Basic, or the Java equivalents, such as Borland’s JBuilder
and IBM’s Visual Age for Java.
The component principle applies to non-visual components also. Whole distributed applications can be created from components. One of the benefits of distributed component
frameworks is that they can provide language independence. Using CORBA, for example, components written in C can communicate with those written in OO languages such
as Java and Smalltalk.
15
1
03 0672323842 CH01
3/20/02
9:26 AM
Page 16
16
Day 1
In Java, there are several component frameworks from which to choose. The J2EE platform uses components extensively to provide modularity within the layers of an application. As such
• A Java component is yet another way of using modularity to house all packages
required to perform a specific task. In the 3-tier environment, for example, the
functionality of the Data Access Logic layer would be split into multiple components.
• A component will publish its interface defining the functionality it offers. This
functionality can then be used by the application itself or by other components.
Benefits of Modularity
If separate parts of the design can be identified, the most appropriate developers can be
tasked with the implementation simultaneously. Some components can also be purchased
from third parties and integrated quite easily because all components will conform to the
framework. This brings down the time to market and is, therefore, a significant cost benefit.
The system is more maintainable if identifiable parts are capable of being upgraded and
re-implemented without hindering the existing running of the system. With modularity
comes the possibility of loose coupling, which means the system itself is extendable
without introducing dependencies. If a module has loose coupling, its maintenance is
simpler.
Using components within layers allows you to further modularize the functionality in
those layers.
Benefits of the 3-Tier Scenario
A modern n-tier application architecture, such as that provided by J2EE, involves the
separation of functionality both by using layers and tiers and also the use of components
within those layers (and objects within those components).
Now, presentational developers need not know anything of the business rules in the system, and any changes to any of the layers should not impact the effectiveness of any of
the others. This aids in maintenance of the system and promotes scalability and extensibility. The separation into components helps with the division of tasks even further.
With the advent of the Internet, enabling more businesses to deliver goods and services
online, it is easier to deliver functionality to customers and business users. There may be
issues with particular versions of browsers, but compared to the situation where thick
03 0672323842 CH01
3/20/02
9:26 AM
Page 17
The Challenge of N-Tier Development
client applications would need to be distributed and installed on each client machine, the
relative merit of distributing functionality using the Internet remains overwhelming considering the potential user base companies are striving to reach.
It is this emphasis on the Internet that many enterprise service vendors are seeking to
exploit. Most organizations have some form of Web presence and many are trying to use
this to offer services to their customers. Since such services interact directly with the
customer, their levels of reliability and usability must be high. Such Web-based applications are now common currency, and the world is evolving further. Web services are
being discussed as the next generation of n-tier development, allowing applications to be
created from components distributed across the Internet. As this model evolves, the distributed Internet becomes the computer.
Enterprise applications can be Web centric, but need not be. To cover Web-centric programming, this book shows how to integrate Servlets (see Day 12, “Servlets”) and Java
Server Pages (JSPs) (see Day 13, “JavaServer Pages”) into Enterprise applications.
Within an organization, or even when creating business-to-business (B2B) links, enterprise applications need not use Servlets or JSPs. In this case, clients may connect directly
to business components, in the shape of Enterprise JavaBeans (see Day 4, “Introduction
to EJBs”), over RMI or CORBA.
As the provision of functionality over the Internet gains importance, most companies will
expose the functionality of their internal applications as part of a Web-based application.
As functionality is exposed, it becomes important to maintain the integrity of the data in
the corporate systems. Transactions provide a common mechanism for doing this.
Transactions are covered in detail in Day 8, “Transactions and Persistence.”
A Model for Enterprise Computing
So, an n-tier, component-based, Web-friendly environment is needed, but what about the
detail—what specific functionality is needed to support such applications? When considering what is needed for a distributed environment, we can turn to an organization that
has been involved in this area for a long time. Since its inauguration in 1989, the OMG
has been working with key industry players (such as 3Com Corporation, American
Airlines, Canon, Inc., Data General, Hewlett-Packard, Philips Telecommunications N.V.,
Sun Microsystems, and Unisys Corporation) to produce a component-based software
market by hastening the introduction of standardized object software. Many of the
OMG’s specifications have become a standard part of the Distributed Object Computing
landscape, such as the Common Object Request Broker Architecture (CORBA), the
Internet Inter-Orb Protocol (IIOP), and the Unified Modeling Language (UML).
17
1
03 0672323842 CH01
3/20/02
9:26 AM
18
Page 18
Day 1
By examining some of the key requirements outlined in the OMG’s Enterprise
Computing Model, it is possible to explore what is required from a modern distributed
computing environment.
Lifecycle
There must be a safe mechanism for creating, copying, moving, and deleting distributed
objects. A distributed component environment must provide containers to manage the
lifetime of components and assist in their deployment. There are also other lifecycle
issues that must be addressed in a distributed environment. For example, distributed
garbage collection (getting rid of unused objects) can be handled in different ways
according to the operating environment. With Java’s Remote Method Invocation (RMI), a
distributed leasing mechanism is used. With CORBA, there are lifecycle management
services. Microsoft’s Distributed COM (DCOM), on the other hand, relies on objects
controlling their own lifetimes.
Persistence
In an enterprise application, you need to be able to store data permanently for later
retrieval. Object Database Management Systems (ODBMS) and Relational Database
Management Systems (RDBMS) commonly support this requirement. A distributed
application environment must provide a way of accessing and updating persistent data in
a simple yet flexible way. It is also important to support different types of data persistence (different databases, legacy systems, and so on) and different ways of accessing
this data (locally or across a network). Any help that can be given to the developer for
data persistence is generally very welcome.
Naming
Distributed applications will be formed from components that reside on different
machines. The parts of the application that use components on other machines must be
able to locate and invoke such components. What is needed is a directory service in
which components or services can register themselves. Any part of the application that
wants to use such a service can look up the location of the service and retrieve information about how to contact it.
Common directory services and protocols include the following:
• CORBA Common Object Services (COS) Naming Service—This allows you to
store object references in a namespace. The COS naming service is widely used in
Java-based distributed environments as a way of storing information about the
location of remote objects. Further information on COS Naming can be found
online at http://www.omg.org.
03 0672323842 CH01
3/20/02
9:26 AM
Page 19
The Challenge of N-Tier Development
• X.500—This defines an information model for storing hierarchical information and
an access protocol called the Directory Access Protocol (DAP). Further information about X.500 can be found online at http://java.sun.com/products/
jndi/tutorial/ldap/models/x500.html.
• Lightweight Directory Access Protocol (LDAP)—This is a lightweight version of
the X.500 protocol that runs over TCP/IP. Further information on LDAP can be
found online at http://www.openldap.org.
• Domain Name System—This is an Internet protocol that allows translation between
host names and Internet addresses. DNS is used by all Internet clients, such as Web
browsers. More information on DNS can be found at
http://www.dns.net/dnsrd/rfc/.
• Microsoft Active Directory—The Active Directory service allows organizations to
store central information on data and services within an enterprise. Further information on Active Directory can be found online at http://www.microsoft.com/
windows2000/technologies/directory/default.asp.
Transaction
In a distributed enterprise application, certain business processes will involve multiple
steps. For example, a typical exchange of goods or services for payment will need to take
payment details, verify those payment details, allocate the goods to be shipped, arrange
the shipping, and take the payment. At any stage, the customer might be interrupted or
the server could crash, not completing the entire transaction. If that happens, the enterprise application must be able to retrieve the previous state to continue with the transaction at a later time or to roll back the transaction so that the system is restored to its original state.
Transaction services provide a way of grouping updates to data so that either all of the
updates are performed or none of them are performed. A transaction coordinator will be
responsible for ensuring this. Transaction information is persisted so that the state of a
transaction can survive a system crash. Transactions can be propagated across distributed
method calls and even across message-based systems.
Security
A secure enterprise application environment will provide the following:
• Authentication—Are you who you say you are?
• Authorization—Are you permitted to do things you are requesting to do?
In addition to this, many enterprise application environments will support both programmatic and declarative security. Programmatic security is enforced within the enterprise
application itself, while declarative security is enforced by the enterprise application
19
1
03 0672323842 CH01
20
3/20/02
9:26 AM
Page 20
Day 1
environment within which the application runs. The enterprise application environment
will consult configuration information to decide which security restrictions to enforce for
a particular application. Changes to this information would not necessitate recompilation
of the application itself.
Java 2 Enterprise Edition (J2EE)
J2EE is an on-going standard for producing secure, scalable, and highly-available enterprise applications. The standard defines which services should be provided by servers
that support J2EE. These servers will provide J2EE containers in which J2EE components will run. The containers will provide a defined set of services to the components.
The J2EE specification provides a definition from which enterprise vendors can produce
J2EE application servers on which J2EE-compliant applications can be deployed. An
impressive list of expert group members produced the latest version of the associated
Java Specification Request (JSR 58 which contains the standard definition for J2EE version 1.3), which can be found online at http://java.sun.com/j2ee/sdk 1.3/
index.html.
Although the J2EE specification defines a set of services and component types, it does
not contain information on how to arrange the logical architecture into physical
machines, environments, or address spaces.
The J2EE platform provides a common environment for building secure, scalable, and
platform-independent enterprise applications. Many businesses are now delivering goods
and services to customers via the Internet by using such J2EE-based servers. The
requirements of such an environment demand open standards on which to build applications, for example,
• Java 2 Platform, Standard Edition (J2SE), a platform independent language
• Components that deliver Web-based user interfaces
• Components to encapsulate business processes
• Access to data in corporate data stores
• Connectivity to other data sources and legacy systems
• Support for XML, the language of B2B e-commerce
Components and Containers
J2EE specifies that a compliant J2EE application server must provide a defined set of
containers to house J2EE components. Containers supply a runtime environment for the
components. As such, Java 2 Platform, Standard Edition (J2SE) is available in each container. Application Programming Interfaces (APIs) in J2EE are also made available to
03 0672323842 CH01
3/20/02
9:26 AM
Page 21
The Challenge of N-Tier Development
21
provide communication between components, persistence, service discovery, and so on.
Containers are implemented by J2EE application server vendors and there should be a
container available for each type of J2EE component:
• Applet Container
• Application Client Container
• Web Container
• EJB Container
There are two types of components deployed, managed, and executed on a J2EE Server:
• Web components—A Web component interacts with a Web-based client, such as a
Web browser. There are two kinds of Web components in J2EE—Servlet
Component and Java Server Pages (JSP) Component. Both types handle the presentation of data to the user. Please see Days 12 and 13 for further details.
• EJB components—There are three kinds of Enterprise JavaBean components—
Session beans, Entity beans, and Message-Driven Beans. Please see Day 4, Day 5,
“Session EJBs,” Day 6, “Entity EJBs,” and Day 10, respectively, for further information.
Figure 1.5 shows the overall relationships between the different containers and components in the J2EE environment.
FIGURE 1.5
J2EE Server
J2EE logical architecture.
Web Container
Browser
Servlet
JSP Page
Database
Application Client
Container
Application
Client
EJB Container
Enterprise
Bean
Enterprise
Bean
J2EE Standard Services
Containers must provide each type of component with a defined set of services that are
covered in detail as you progress through the book. Briefly these services consist of
1
03 0672323842 CH01
22
3/20/02
9:26 AM
Page 22
Day 1
• Connectivity—Containers must support connectivity to other components and to
application clients. One form of required connectivity is to distributed objects
through both Java Remote Method Invocation (RMI) and CORBA (as implemented
by the Java IDL package and RMI over IIOP). Internet connectivity must be provided both through the Hypertext Transport Protocol (HTTP) and its secure form
(HTTPS).
• Directory services—J2EE servers are required to provide naming services in which
components can be registered and discovered. The Java Naming and Directory
Interfaces (JNDI) provide a way of accessing these services.
• Data access and persistence—Data access is provided through the Java Database
Connection API (JDBC). This API works both at the application level to interface
with databases and also service providers who build drivers for specific databases.
• Legacy connectivity—The Java Connector Architecture (JCA or Connectors) provides J2EE support in integrating Enterprise Information Servers and legacy systems, such as mainframe transaction processing and Enterprise Resource Planning
(ERP) systems. This support extends to J2EE service providers who are writing
adapters to connect other systems to the J2EE enterprise architecture.
• Security—Security is built into the J2EE model. APIs, such as the Java
Authentication and Authorization Service (JAAS), assist the J2EE enterprise application in imposing authentication and authorization security checks on users.
• XML Support—The JAXP API supports the parsing of XML documents using
Document Object Model (DOM), SimpleAPI for XML documents (SAX), and the
eXtensible Stylesheet Language Transformations (XSLT).
• Transactions—A J2EE server must provide transaction services for its components.
The boundaries of transactions need to be specified by the container or the application. The container will usually take responsibility for transaction demarcation,
although the Java Transaction API (JTA) allows the component to control its own
transactions if required.
• Messaging and e-mail—The Java Message Service (JMS) allows components to
send and receive asynchronous messages, typically within an organizational boundary. The JavaMail API enables Internet mail to be sent by components and also
provides functionality to retrieve e-mail from mailstores. JavaMail uses the
JavaBeans Activation Framework (JAF) to support various MIME types.
Figure 1.6 shows the J2EE architecture updated with the services available to its containers. All of these services are discussed in more detail tomorrow.
Every J2EE-compliant server must support the services defined in this section. To provide a concrete example of how services should work, the team working on the J2EE
03 0672323842 CH01
3/20/02
9:26 AM
Page 23
The Challenge of N-Tier Development
23
JSR is responsible for providing a Reference Implementation (RI) of the J2EE APIs. This
RI is freely available from Sun and provides a convenient platform for prototyping applications and testing technologies.
FIGURE 1.6
J2EE Server
Applet
Container
Web Container
Connections
JTA
JAF
JAAS
JSP Page
JDBC
JavaMail
J2SE
JMS
Servlet
HTTP
Applet
Client
JAXP
The J2EE platform
with services available.
HTTP
Database
Applet
Container
EJB Container
JTA
JAF
JAAS
Connections
J2SE
Enterprise
Bean
JDBC
JAXP
JMS
JAAS
JDBC
JAXP
JMS
JavaMail
Enterprise
Bean
Application
Client
J2EE Blueprints
The J2EE Blueprints are a set of best practices that show how best to implement J2EE
applications. The Blueprints provide a concrete implementation of Sun’s vision for 3-tier,
J2EE-based systems. There is a download available from the Sun Web site called Java
Pet Store Sample Application that shows these best practices. This can be found online at
http://java.sun.com/j2ee/blueprints.
The Java Pet Store is a typical online e-commerce application. The best practices cover
application design and, in particular, the promotion of the following:
• Code reuse
• Logical functional partitioning
• The separation of areas of high maintenance
• Extensibility
• Modularity
• Security
• Simple and consistent user interface
1
03 0672323842 CH01
3/20/02
9:26 AM
Page 24
24
Day 1
• Efficient network usage
• Data integrity
The J2EE Blueprints will show you step-by-step how to design multi-tier enterprise
applications. There are explorations on the following topic areas:
• The Client Tier
• The Web Tier
• The Enterprise JavaBeans Tier
• The Enterprise Information Systems Tier
• Design Patterns
In addition, there are discussions on how to package and deploy your enterprise applications.
J2EE Compatibility Test Suite
Enterprise service providers will sell more if their enterprise servers meet with the J2EE
specification requirements. To enable them to test their products against the specification,
Sun Microsystems Inc. offers a testing environment. Servers that pass all of the tests can
be certified as J2EE compliant. Further details of the compatibility suite can be found
online at http://java.sun.com/j2ee/compatibility.html.
The following are some examples of application vendors and their servers that have been
certified by Sun Microsystems as being compatible with the J2EE specification (some of
these may not yet have attained J2EE 1.3 certification, so please check on the Sun Web
site).
• Allaire (www.macromedia.com/software/coldfusion)—ColdFusion 5 comes with
its own markup language (ColdFusion Markup Language) that integrates with all
popular Web languages and technologies. ColdFusion works with multi-tier architectures through COM, CORBA, and EJB integration.
• BEA Systems (www.bea.com)—BEA Weblogic Server includes support for Web
Services, J2EE Connector Architecture, and updated J2EE services, with EJB 2.0,
Servlet 2.3, and JSP 1.2.
• IBM (www.ibm.com)—Websphere Commerce Business Edition supports EJB, JSP,
XML, HTTP, and wireless markup language technologies.
• iPlanet (www.iplanet.com)—iPlanet Application Server Enterprise Edition supports the J2EE platform and is integrated with transaction monitor, iPlanet Web
Server, and iPlanet Directory Server. It supports XML, wireless application protocols, Simple Network Management Protocol (SNMP), LDAP, CORBA, and JDBC.
03 0672323842 CH01
3/20/02
9:26 AM
Page 25
The Challenge of N-Tier Development
• JBoss (www.jboss.org)—JBoss is a freeware server that houses an implementation
of the EJB 1.1 (and parts of 2.0) specification. It is similar to Sun’s J2EE
Reference Implementation, but the JBoss core server provides only an EJB server.
JBoss does not include a Web container, but JBoss is available to download with a
freeware Web server.
• Persistence (www.persistence.com)—PowerTier Release 7 for J2EE supports Java,
EJB deployment, and Rational Rose integration.
The Future of J2EE
Probably the major area for the future of J2EE is that of Web Services. There are a number of JSRs active at the time of this writing on the following topic areas:
• JSR 67 Java APIs for XML Messaging (JAXM) 1.0—Message-based communication between Web Services and Web Service clients. Please refer to Day 21, “Web
Service Registries and Message-style Web Services,” for further details.
• JSR 93 Java APIs for XML Registries 1.0 (JAXR)—Registry and naming service
access for Web Services. Please refer to Day 21 for further details.
• JSR 101 Java APIs for XML-RPC (JAX-RPC)—RPC-style interaction with Web
Services. Please refer to Day 20, “Using RPC-Style Web Services with J2EE,” for
further details.
• JSR 109 Implementing Enterprise Web Services—A model of how Web Services
should work within J2EE.
These JSRs can be found through the Java Community Process (JCP) Web site at
http://www.jcp.org.
Summary
Enterprise application development has helped businesses provide Web-enabled, scalable,
secure applications quickly. It has also enabled vendors to produce pluggable tools and
services to augment the J2EE standard defined through the Java Community Process.
This chapter describes the journey towards the n-tier environment that underpins the
architecture of enterprise application programming. You have investigated the basic services that should be available to an n-tier enterprise application, and examined a few of
the enterprise application servers on the market.
Q&A
Q I have a monolithic program that I would like transition into an n-tier application. How do I do this?
25
1
03 0672323842 CH01
3/20/02
9:26 AM
26
Page 26
Day 1
A First you need to identify what sort of target architecture is required. If your application is to be Web-enabled, you will need to provide Web-oriented functionality in
the presentation layer. If you are working with persistent data, you will need data
access through a data access layer. You should map out your target architecture
based on the services available under the J2EE platform.
Next, you will need to sift through the monolithic code separating out the code
belonging to the logical layers. This code might need to be rewritten in such a way
as to make it maintainable and extensible. Introduce modularity by adopting
object-oriented programming and design classes. Package these classes and design
components to have maximum cohesion and loose coupling wherever possible.
To implement and deploy your J2EE application, read the rest of the book and follow the examples.
Q What is the difference between Microsoft’s .NET framework and J2EE?
A You can build enterprise applications with both platforms. Both J2EE and .NET
framework applications can provide good levels of scalability, availability and so
forth. The essential difference is largely one of choice. J2EE lets you use any operating system, such as Windows, UNIX, or a mainframe. J2EE’s development environment can be chosen to suit developers from a variety of Integrated Development
Environment (IDE) and J2EE application server vendors. The .NET framework is
essentially limited to the Windows family of operating systems. This allows it to be
more cleanly integrated with the operating system, but reduces the choice of target
platform.
Exercises
To extend your knowledge of n-tier development, try the following exercises:
1. Write a design for a monolithic application to provide a shopkeeper with data concerning stock information.
2. Redesign the application based on the contents of this chapter, so as to make it
accessible over the Internet.
3. Visit http://java.sun.com/j2ee for further details of the J2EE programming
tools and utilities.
4. Visit http://www.microsoft.com for further details of the .NET framework.
Compare and contrast this with the facilities available under J2EE.
04 0672323842 CH02
3/20/02
9:37 AM
Page 27
WEEK 1
DAY
2
The J2EE Platform and
Roles
Yesterday, you learned about enterprise computing and some of the problems
facing developers of enterprise solutions. The day also introduced J2EE, a technology that can help you develop secure, scalable, and platform-independent
solutions that meet the needs of today’s business.
Today, you will explore the J2EE platform and see what it can offer you to help
solve your business problems. J2EE is a large framework that boasts of a widerange of components, services, and roles. It is these that you explore today, so
that you’ll be eager and prepared to start writing code tomorrow. The following
are the major topics today covers:
• Understanding how J2EE delivers solutions for today’s business
• Introducing the available Web-centric components
• Introducing the use of Enterprise JavaBeans
• Assessing platform roles
• Exploring the packaging and deployment of enterprise applications
04 0672323842 CH02
3/20/02
9:37 AM
Page 28
28
Day 2
Revisiting the J2EE Platform
You learned a lot about enterprise computing yesterday. You learned specifically about
how business needs force the evolution of application architectures; today, most applications are distributed across multiple machines. This approach, the n-tier model, gives
rise to different ways of writing and structuring applications. Units of functionality—
components—provide modularity that allow multiple developers to work more easily on
different parts of the application. Use of a component framework also allows developers
to apply third-party components to speed development. These loosely-coupled components may run as an application on a desktop client, within a Web server, or even on a
server that connects to a legacy system. In addition, data has undergone a revolution.
Data sources now go beyond simple, relational databases containing tables to encompass
databases that contain serialized objects or plain text files containing XML. Alternatively,
data may take the form of user information in an LDAP directory or information in an
Enterprise Resource Planning (ERP) system.
Applications written in traditional programming languages that do not have supporting
frameworks simply cannot perform the operations required by today’s environment.
Instead, you must employ component-aware programming languages together with
frameworks dedicated to enterprise computing. As you have already seen, J2EE is such a
framework. Although the environment within which J2EE operates might sound daunting, J2EE isn’t. When you write J2EE applications, you still write Java code, and you
still get to use the J2SE classes with which you are familiar.
To successfully use J2EE, you must
• Install and configure your J2EE environment
• Understand J2EE roles
• Appreciate the purpose of containers
• Understand how you can use J2EE components
• Understand the services that containers supply to components
• Learn or explore a new set of APIs
Yesterday’s lesson introduced the first four points in the list. You will explore them in
more depth today. After you understand these, you will be ready for tomorrow, when you
will start to apply the new APIs and to code real applications against them.
Using Sun Microsystems’ J2EE SDK
Before you can start coding real J2EE applications, you need a J2EE implementation and
a Java development environment, such as Sun Microsystems JDK or a Java Integrated
04 0672323842 CH02
3/20/02
9:37 AM
Page 29
The J2EE Platform and Roles
29
Development Environment (IDE). This book uses the Sun Microsystems’ J2EE SDK,
which is a complete reference implementation of J2EE. It includes all the classes, containers, and tools you need to learn J2EE.
To run the example code provided on the CD-ROM accompanying this book, you will
also need to install a sample database. Installing the sample database is described in the
Exercise at the end of this day’s lesson. But now, to give you some hands-on work
before you study the theory behind J2EE, you will install the J2EE SDK 1.3 on your
workstation.
Note
The J2EE SDK is free to download, use for learning J2EE, and use as a development tool. The Sun Microsystems license for this product states that you
may not use it in a production environment. Be warned!
Installing J2EE SDK 1.3
Before you download the SDK, ensure that you have J2SE 1.3.1_01 (also known as JDK
1.3.1) or later correctly installed and are using one of the following supported platforms:
• Windows NT4 or 2000
• Solaris SPARC 7 or 8
• Linux Redhat v 6.0 or 6.1
You can use a Java IDE that supports J2SE 1.3 (or later) in preference to the Sun
Microsystem’s J2SE JDK 1.3.1.
Before installing J2EE SDK 1.3, you must uninstall any previous versions of the J2EE
SDK.
Finally, you must ensure that you have a JAVA_HOME environment variable that points to
the location of the directory where you installed J2SE SDK (or your preferred Java IDE).
This should have been defined when you (or your administrator) installed the J2SE SDK
(JDK). If the JAVA_HOME variable is not defined, you must define it now as follows.
If you are using Windows NT or 2000 (remember J2EE SDK is not supported on other
Windows’ platforms) you should set the JAVA_HOME variable in your system environment
so that it is defined for all of the programs you run. Do this using the Control Panel as
follows:
1. Within the Control Panel, select System.
2. The System Properties dialog appears, select the Advanced tab.
3. Click Environment Variables.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 30
30
Day 2
4. The Environment Variables dialog appears, click New.
5. The New System Variable dialog appears, enter the name and value of the variable.
Assuming that you installed the J2EE SDK 1.3 on the C: drive using the default
directory name, you will set the JAVA_HOME variable to
C:\j2sdkee1.3
6. Click OK to clear each of the dialogs.
You must have administrator privileges to edit or change system environment variables. If you do not have administrator privilege, you can still use the JDK, but
you’ll have to define the variables in your user environment. Any other users of
your workstation will also have to define the same variables in their environment.
If you are using Linux or Unix and the JAVA_HOME environment variable does not exist,
you can set it with the following command(you must be using the Bourne, Korn, Bash or
compatible shell):
JAVA_HOME=/usr/local/jdk1.3.1
export JAVA_HOME
This example assumes you have installed the Sun Microsystems’ JDK 1.3.1 in
/usr/local.
Typically, you will add these variable definitions to your login environment by adding
the same two lines to the.profile file in your home directory.
Finally, you should ensure that the JDK bin directory is in your search path (again this
should already be configured on your workstation).
For Windows users, if your search path does not contain the JDK bin directory, add the
following directory to your PATH via the Control Panel:
%JAVA_HOME%\bin
For Linux/Unix users, if your search path does not contain the JDK bin directory, add
the following line to your .profile file:
PATH=$PATH:$JAVA_HOME/bin
Now download the J2EE SDK in the format appropriate to your system from
http://java.sun.com/j2ee/sdk_1.3/index.html. You should download the J2EE SDK
to a temporary directory because the installation process will ask you where to install the
SDK.
The installation of the SDK is quite straightforward; just follow the instructions for your
platform:
04 0672323842 CH02
3/20/02
9:37 AM
Page 31
The J2EE Platform and Roles
31
• Windows—Double-click the icon of the j2sdkee-1_3_01-win.exe file and follow
the onscreen instructions.
• Solaris—Issue the following commands to make the download bundle executable
and run the installation::
chmod a+x ./j2sdkee-1_3__01-solsparc.sh
./j2sdkee-1_3__01-solsparc.sh
• Linux—Change directories to the required parent directory for the J2EE SDK (for
example, /usr/local) and extract the download bundle using the following command:
tar – xzvf <download_directory>/j2sdkee-1_3_01-linux.tar.gz
Now you must:
1. Define the J2EE_HOME variable.
2. Add the J2EE SDK bin directory to your search path.
3. Add the J2EE classes to your CLASSPATH.
You have already been shown how to define variables and change your path for your
environment (Windows users use the Control Panel and Linux/Unix users add variable
definition lines to .profile), so making these changes will be straightforward.
Windows users must (assuming the J2EE SDK was installed on the C: drive)
1. Define the following environment variable
J2EE_HOME=c:\j2eesdk1.3
2. Add the following directory to the end of the PATH variable:
%J2EE_HOME%\bin
3. Add the J2EE JAR files to the CLASSPATH variable:
%J2EE_HOME%\lib\j2ee.jar;%J2EE_HOME%\lib\locale
If your CLASSPATH variable is not currently defined, you must ensure that it
includes the current directory so the full setting will be
.;%J2EE_HOME%\lib\j2ee.jar;%J2EE_HOME%\lib\locale
Linux/Unix users must add the following to their .profile (assuming the J2EE SDK
was installed in /usr/local/j2eesdk1.3):
1. Define the following environment variable:
J2EE_HOME=/usr/local/j2eesdk1.3
2. Update the PATH variable for J2EE SDK:
PATH=$PATH:$J2EE_HOME/bin
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 32
32
Day 2
3. Add the J2EE JAR files to the CLASSPATH variable:
CLASSPATH=$CLASSPATH:$J2EE_HOME/lib/j2ee.jar:$J2EE_HOME/lib/locale
If your CLASSPATH variable is not currently defined, you must ensure that it
includes the current directory so the full setting will be:
CLASSPATH=.:$J2EE_HOME/lib/j2ee.jar:$J2EE_HOME/lib/locale
That is it! You are now ready to start using the J2EE SDK.
Note
All the documentation for the J2EE utility programs and class files is contained in the J2EE SDK download bundle. You will find the documentation
in the docs sub-directory of the J2EE installation directory.
Starting the J2EE Reference Implementation (RI)
The J2EE SDK includes a Reference Implementation of J2EE. This Reference
Implementation (RI) contains the following software programs:
• A J2EE server
• A relational database called Cloudscape
• An HTTP (Web) server
• A JNDI service implementation
• A JMS service implementation
• Class files for the J2EE APIs
• Various administration and support utilities
The server software components of the RI (database, JNDI, Web server, and J2EE server)
are purely for development and are not designed for commercial use. The J2EE RI has
been used for the code shown in this book because it is free of charge and conforms to
the J2EE 1.3 specification.
To develop J2EE applications and run the example code presented in this book, you will
need to start the J2EE server and the Cloudscape database server. Starting the J2EE
server will also start the JNDI, JMS, and HTTP servers provided with the J2EE RI.
There are no graphic tools for managing the J2EE and Cloudscape servers, so you will
have to start them from the command line. Each server should be started in a separate
command window or Telnet window if you are not using a graphical console. It does not
matter in which order you start the J2EE and Cloudscape servers.
04 0672323842 CH02
3/20/02
9:37 AM
Page 33
The J2EE Platform and Roles
33
In the following examples, you must have configured your search path (the PATH environment variable) to include the J2EE SDK bin directory as shown previously.
To start the J2EE RI server, create a new window with access to a command-line prompt
(command window for Windows NT/2000 or a terminal or shell window for Linux/Unix
users). Enter the following command at the prompt:
j2ee –verbose
This will start the J2EE server in the current window with diagnostic messages displayed
in the window. If (or when) you have problems deploying your J2EE applications to the
server, it is this window you should examine for error messages. If you put simple diagnostic messages in your EJBs that write to System.out or System.err, these messages
will also appear in this window.
Listing 2.1 shows the diagnostic messages issued as the J2EE RI and associated servers
startup.
LISTING 2.1
Successful J2EE Reference Implementation Startup Diagnostics
1: > j2ee -verbose
2: J2EE server listen port: 1050
3: Naming service started:1050
4: Binding DataSource, name = jdbc/DB2,
➥url = jdbc:cloudscape:rmi:CloudscapeDB;create=true
5: Binding DataSource, name = jdbc/DB1,
➥url = jdbc:cloudscape:rmi:CloudscapeDB;create=true
6: Binding DataSource, name = jdbc/InventoryDB,
➥url = jdbc:cloudscape:rmi:CloudscapeDB;create=true
7: Binding DataSource, name = jdbc/Cloudscape,
➥url = jdbc:cloudscape:rmi:CloudscapeDB;create=true
8: Binding DataSource, name = jdbc/EstoreDB,
➥url = jdbc:cloudscape:rmi:CloudscapeDB;create=true
9: Binding DataSource, name = jdbc/XACloudscape, url = jdbc/XACloudscape__xa
10: Binding DataSource, name = jdbc/XACloudscape__xa, dataSource = COM.cloudscape.core.RemoteXaDataSource@653220
11: Starting JMS service...
12: Initialization complete - waiting for client requests
13: Binding: < JMS Destination : jms/Queue , javax.jms.Queue >
14: Binding: < JMS Destination : jms/firstQueue , javax.jms.Queue >
15: Binding: < JMS Destination : jms/Topic , javax.jms.Topic >
16: Binding: < JMS Cnx Factory :
➥QueueConnectionFactory , Queue , No properties >
17: Binding: < JMS Cnx Factory :
➥jms/QueueConnectionFactory , Queue , No properties >
18: Binding: < JMS Cnx Factory :
➥TopicConnectionFactory , Topic , No properties >
19: Binding: < JMS Cnx Factory :
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 34
34
Day 2
LISTING 2.1
Continued
➥jms/TopicConnectionFactory , Topic , No properties >
20: Starting web service at port: 8000
21: Starting secure web service at port: 7000
22: J2EE SDK/1.3
23: Starting web service at port: 9191
24: J2EE SDK/1.3
25: J2EE server startup complete.
If you start the J2EE server without the -verbose option, all the diagnostic messages will
be written to log files in the logs sub-directory of the J2EE SDK installation directory.
You will find additional logging information is also written to these log files even with
the -verbose option specified.
The J2EE log files are stored in a sub-directory named after your workstation in the logs
sub-directory of the J2EE SDK installation directory. Further sub-directories are used to
separate the log files for the J2EE, JMS, and HTTP servers.
To start up the Cloudscape database server, you must open a new window and enter the
following command:
cloudscape –start
Again, some simple diagnostic messages will be displayed as the server starts up, as
shown in Listing 2.2.
LISTING 2.2
Successful Cloudscape Startup Diagnostics
> cloudscape -start
Thu Jan 10 10:52:18 GMT+00:00 2002:
➥ [RmiJdbc] Starting Cloudscape RmiJdbc Server
Version 1.7.2 ...
Thu Jan 10 10:52:25 GMT+00:00 2002:
➥ [RmiJdbc] COM.cloudscape.core.JDBCDriver registered in DriverManager
Thu Jan 10 10:52:25 GMT+00:00 2002: [RmiJdbc] Binding RmiJdbcServer
Thu Jan 10 10:52:25 GMT+00:00 2002:
➥ [RmiJdbc] No installation of RMI Security Manager
Thu Jan 10 10:52:26 GMT+00:00 2002:
➥ [RmiJdbc] RmiJdbcServer bound in rmi registry
Troubleshooting J2EE and Cloudscape
You should have no problems starting up either server. If you do have problems, check
the error messages displayed in the relevant window. The most likely problems are discussed in the rest of this section.
04 0672323842 CH02
3/20/02
9:37 AM
Page 35
The J2EE Platform and Roles
35
Read Only Installation Directory
You will not be able to run J2EE RI and Cloudscape unless you have installed the J2EE
SDK in a writeable directory. If you have installed the J2EE SDK as a privileged user
(Administrator, root, or whomever), make sure that you grant your normal login account
read and write permission to the installation directory and all contained files and directories.
Server Port Conflicts
Although the J2EE SDK software uses TCP port numbers that are not normally used by
other software, there is always a possibility that there will be a port number conflict.
If a port is used by another software server, a J2EE component will fail to start up and
you will see an error message stating that the server “Could not connect to a required
port.” The error will normally include a stack trace.
The most likely cause of a port conflict is where you (or another developer) have already
started the J2EE server. Listing 2.3 shows the error message for this situation.
LISTING 2.3
Error Message Caused by Running the J2EE Server Twice
> j2ee -verbose
J2EE server listen port: 1050
org.omg.CORBA.INTERNAL:
minor code: 1398079697 completed: No
at com.sun.corba.ee.internal.iiop.
➥GIOPImpl.createListener(GIOPImpl.java:256)
at com.sun.corba.ee.internal.iiop.
➥GIOPImpl.getEndpoint(GIOPImpl.java:205)
at com.sun.corba.ee.internal.iiop.
➥GIOPImpl.initEndpoints(GIOPImpl.java:140)
at com.sun.corba.ee.internal.POA.POAORB.
➥getServerEndpoint(POAORB.java:488)
at com.sun.corba.ee.internal.POA.POAImpl.
➥pre_initialize(POAImpl.java:154)
at com.sun.corba.ee.internal.POA.POAImpl.<init>(POAImpl.java:112)
at com.sun.corba.ee.internal.POA.POAORB.makeRootPOA(POAORB.java:110)
at com.sun.corba.ee.internal.POA.POAORB$1.evaluate(POAORB.java:128)
at com.sun.corba.ee.internal.core.Future.evaluate(Future.java:21)
at com.sun.corba.ee.internal.corba.ORB.
➥resolveInitialReference(ORB.java:2421)
at com.sun.corba.ee.internal.corba.ORB.
➥resolve_initial_references(ORB.java:2356)
at com.sun.enterprise.server.J2EEServer.run(J2EEServer.java:193)
at com.sun.enterprise.server.J2EEServer.main(J2EEServer.java:913)
java.lang.RuntimeException: Unable to create ORB.
➥ Possible causes include TCP/IP ports in use by another process
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 36
36
Day 2
LISTING 2.3
Continued
at com.sun.enterprise.server.J2EEServer.run(J2EEServer.java:203)
at com.sun.enterprise.server.J2EEServer.main(J2EEServer.java:913)
java.lang.RuntimeException: Unable to create ORB.
➥Possible causes include TCP/IP ports in use by another process
at com.sun.enterprise.server.J2EEServer.run(J2EEServer.java:203)
at com.sun.enterprise.server.J2EEServer.main(J2EEServer.java:913)
java.lang.RuntimeException: Unable to create ORB.
➥Possible causes include TCP/IP ports in use by another process
at com.sun.enterprise.server.J2EEServer.run(J2EEServer.java:350)
at com.sun.enterprise.server.J2EEServer.main(J2EEServer.java:913)
J2EE server reported the following error: Unable to create ORB.
➥Possible causes include TCP/IP ports in use by another process
Error executing J2EE server ...
You cannot run more than one J2EE RI server on the same workstation.
If your port conflict is with another piece of software, try to disable this software when
running J2EE RI and Cloudscape. If this is not possible, you can change the port numbers used by each J2EE server by editing the file called server.xml in the conf subdirectory of the J2EE SDK installation directory. The definitions of the default server
port numbers are obvious if you are used to reading and editing XML files. Changing the
J2EE RI port numbers is something you should avoid if at all possible.
Applications Failing with a “Connection Refused: no further
Exception
information”
A common error when working with the J2EE RI is to forget to start up the Cloudscape
database server in a separate window. If you fail to start the database and use a J2EE
component, such as an EJB, that accesses the database, you will get the following error:
java.sql.SQLException: Connection refused to host:
➥<hostname>; nested exception is:
java.net.ConnectException: Connection Refused: no further information
To solve this problem, start up the Cloudscape server as described previously and stick a
note to your monitor to remind you to start the database as well as J2EE. If you are confident with batch or shell scripts, you can write your own scripts to start both servers in
separate windows.
The following simple scripts will work for the indicated platforms:
On Windows NT/2000, use
start “J2EE” j2ee –verbose
start “Cloudscape” cloudscape –start
04 0672323842 CH02
3/20/02
9:37 AM
Page 37
The J2EE Platform and Roles
37
On Solaris, use
dtterm –name j2ee –e “j2ee –verbose” &
dtterm –name cloudscape –e “cloudscape –start” &
On Linux, use
xterm –title j2ee –e “j2ee –verbose” &
xterm –title cloudscape –e “cloudscape –start” &
Closing Down J2EE RI and Cloudscape
To close down J2EE RI and Cloudscape, you should use the following commands:
j2ee –stop
cloudscape –stop
These commands can be run from any command window. Remember that the J2EE and
Cloudscape server windows are busy and cannot accept additional commands while the
servers are running.
Although Sun Microsystems do not recommend this approach, typing ^C (Ctrl+C) in the
server window or simply closing the window will also shut down the servers.
Optional Software Used in this Book
A Java IDE (JDK) and a J2EE implementation (J2EE SDK) are all you require to learn
how to develop J2EE applications. However, areas of this book look at using J2EE applications in a wider context and make use of additional (freely available) software.
So that you are aware of this software, Table 2.1 lists the optional software used in this
book. Full instructions for downloading and configuring this software (should you want
to do so) are included in the relevant day’s instructions. You do not need to download this
software at the present time.
TABLE 2.1
Optional Software Used in Daily Lessons
Day
Software
Resource URL
3
OpenLDAP and a Unix
system to run the Open
LDAP server.
http://www.openldap.org/
JSPTL Java Standard Tag
libraries from the
Apache Jakarta project.
http://jakarta.apache.
XALAN from the Apache
project.
http://xml.apache.org/
14
17
software/download/
org/taglibs/index.html
xalan-j/index.html
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 38
38
Day 2
TABLE 2.1
Continued
Day
Software
Resource URL
20
Apache Axis alpha2.
http://xml.apache.org/
axis/index.html
Apache Tomcat 4.0.1.
http://jakarta.apache.org/
tomcat/index.html
JAXM 1.0 reference
implementation (part of
the “JAX Pack Fall 01”).
http://java.sun.com/xml
Understanding Tiers and Components
The 3-tier model splits an application down so that business logic resides in the middle
of the 3 tiers. This is often called the middle tier, business tier, or EJB tier. Throughout
this section, this tier will be referred to as the business tier. The first tier has the role of
providing the interface between the user and the application. Depending on your specific
architecture, this can be known as the client tier or presentation tier. Throughout this section, the term presentation tier will be used to define this client-centric tier.
Most of the code written by J2EE application developers resides in the presentation and
business tiers. The next two sections explore these tiers. You will see that different types
of components are used in each tier to deliver particular application functionality. The
components in different tiers should be loosely coupled. In other words, a component
should not have dependencies on the client or other components. For example, imagine a
business component that processes credit card payments. If the component is self contained, almost any application can pass it payment information and it, in turn, can return
an appropriate response. Figure 2.1 shows such a business component communicating
with a variety of clients:
As you can see, because this business component encapsulates all the payment process
functionality, it is not tied to any component in the presentation tier, so it can serve multiple client types. Code that supports more than one type of client is only one advantage
of component architecture; you will learn about some of the other advantages later in this
chapter.
04 0672323842 CH02
3/20/02
9:37 AM
Page 39
The J2EE Platform and Roles
FIGURE 2.1
Multiple clients
accessing the services
of a business component.
Wireless
Client
39
J2EE Server
JSP Container
Web
Browser
JSP
Desktop
Application
EJB Container
Presentation
Tier
2
Enterprise
Bean
Containing
Payment
Process
Functionality
Business
Tier
The Business Tier
As you have just seen, business components sit in the business tier of a J2EE application.
These business components encapsulate business logic and are used by components in
the presentation tier that deliver this functionality to users of the application.
Benefits of Business Components
Previously, you saw a simple credit card processing component that provided that service
for a number of different clients. This demonstrated just one benefit of component architecture but, in fact, components offer many advantages over a composite architecture:
• Increased efficiency—The division of labor helps a business to roll out applications
quickly. The use of components allows presentation developers to develop GUIs,
process programmers to focus on business logic, and data access experts to focus
on data access.
• Extensibility—You can simply add or remove components to an application so that
it can offer further functionality, for example, if you want to expose application
functionality through a Web Service. The component architecture allows you to
simply add the additional units of functionality the system requires. By the way, if
you don’t know about Web Services, don’t worry. Day 20, “Using RPC-Style Web
Services with J2EE,” and Day 21, “Web Service Registries and Message-Style Web
Services,” show you how to apply a J2EE application as a Web Service.
• Language independence—A modularized system allows you to write code in one
language that communicates with code written in another. For example, you can
04 0672323842 CH02
3/20/02
9:37 AM
Page 40
40
Day 2
access the functionality of a J2EE application from a CORBA client by using RMIIIOP, or from a Microsoft COM client by using the Client Access Services COM
Bridge.
• System upgrade—Inevitably, an organization’s business processes change, and so
too must the application logic. The use of components allows you to change one
component without affecting the other components in the system.
This list is not an exhaustive survey of the benefits of components. But it should make it
clear that components offer both developers and business an ideal way of providing
application functionality.
J2EE defines how various types of components perform specific roles in different tiers.
In the presentation tier, different types of components will be applied to provide functionality for different types of client (application components, applet components, servlet
components, and JavaServer Page components). The business tier houses different types
of business components. In J2EE terms, business components are embodied as Enterprise
JavaBeans. The following sections focus on how such components are applied for the
most common application architecture currently—namely, a business system with a Webbased user interface.
Components: Enterprise JavaBeans
In a typical business application built on the J2EE platform, business logic will be encapsulated in Enterprise JavaBeans (EJBs). It is possible to use other types of component or
plain Java objects to implement business logic, but EJBs provide a convenient way of
encapsulating and sharing common business logic, as well as benefiting from the services provided by the EJB container.
Suppose you were tasked with designing and implementing a typical Web-based,
e-commerce application. Although the precise analysis of the business problem would be
specific to your own environment, you would probably end up with the following flow
through your application:
1. Display your products to the customer.
2. Allow the customer to select one or more products.
3. Confirm the order and take shipping details.
4. Take payment for the items.
5. Deliver the order to your warehousing and distribution systems.
6. [optional] Authenticate the user to access previously stored personal information or
preferences.
04 0672323842 CH02
3/20/02
9:37 AM
Page 41
The J2EE Platform and Roles
41
7. [optional] Generate a report from the items purchased by a particular customer or
on a particular day.
All of these steps involve a certain amount of business logic and data manipulation. For
example, in step 1, your application will need to send pages of HTML to the customer’s
browser containing product descriptions and pricing information. To do this, you will
need to retrieve this product and pricing information from somewhere. The obvious
choice is a database that stores your product catalog information together with pricing
information.
As you will see, it would be quite possible to deliver the catalog functionality you
require simply by using database access code from a Web component such as a
JavaServer Page. However, what if gathering this catalog information was not quite so
straightforward?
• The product and pricing information may be spread across multiple databases.
Even worse, it may be that some of the information must be extracted from (or
delivered to, in the case of submitting an order) a legacy system. This means that
the user-interface component would have to know details about data access.
• There may be extra business processes that need to be applied during the creation
of the catalog. These could range from custom pricing for a specific group or individual through cross-selling of related products and on to the suggested substitution of alternative products for any that are not in stock. This means that the user
interface component must have knowledge of multiple business processes.
• If a customer is to be identified and their preferences reflected, authentication is
required. Also, some information about them and their preferences must be maintained against a database somewhere. This means that the user interface component
must know about authentication and mapping user identity to stored information.
As you can see, the user interface component rapidly becomes its own version of the
monolithic applications you saw yesterday. The business logic associated with all of this
processing should be devolved to EJBs in order that the user interface component can
concentrate on what it does best, namely generating a compelling user interface and
guiding the user through a particular interaction.
What is needed then is for the business logic in the EJB to be made available to other
components that may want to use it. The mechanisms involved must be commonly available to the potential clients to impose minimal overhead on those clients. Hence, the EJB
model makes use of two mechanisms found in J2SE—namely Remote Method
Invocation (RMI) and the Java Naming and Directory Interface (JNDI)—to facilitate
interaction between the EJB and its client. When an EJB is written, the functionality it
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 42
42
Day 2
offers to clients is defined as an RMI remote interface. When an EJB is deployed, its
location is registered in the naming service.
A client will then use JNDI to look up the location of the EJB. It will interact with a factory object, called the EJB’s home, to obtain an instance of the EJB. This is equivalent of
using new to create an instance of a local object. When the client has a reference to the
EJB, it can use the business functionality offered by the EJB. This sequence is shown in
Figure 2.2.
FIGURE 2.2
A client uses JNDI and
RMI to access an EJB.
EJB Container
Client Container
Component
Client
3
2
Call business
methods
Obtain
instance
1 Look up EJB
RMI
Enterprise
Bean
RMI
Home
Factory
Database
There is a more detailed look at how to use EJBs on Day 4, “Introduction to EJBs.”
Within the required business logic, certain components will be primarily concerned with
data and the manipulation of that data, whereas others will focus on the sequencing of
business logic and the associated workflow. Equally, components will interact in different ways, depending on the needs of the application. Some interactions will be synchronous in nature; there is no point in performing the next stage of the process until the current one has finished. Other interactions can be handled asynchronously; an appropriate
message can be sent to another component and then the originator of the message can
carry on to the next stage of the process. This means that clients using asynchronous
interactions complete faster, but they may not be suitable for all applications if there
must be a guarantee that an operation has completed or if return values are required. This
difference between synchronous and asynchronous interactions is shown in Figure 2.3.
04 0672323842 CH02
3/20/02
9:37 AM
Page 43
The J2EE Platform and Roles
43
FIGURE 2.3
Synchronous interactions will result in the
caller waiting for the
function to complete,
whereas asynchronous
interactions allow
callers to proceed
without waiting.
Synchronous interaction
Client
BusinessObject
Asynchronous interaction
Client
BusinessObject1
BusinessObject2
Call function
Return value
Send message
Send message
Because different business components are called on to behave in different ways, there
are multiple types of EJB defined that can be used to encapsulate different parts of an
application’s business logic.
Components: Session Beans
Session EJBs, often just called session beans, are the simplest and probably most common type of EJB. A session bean is primarily intended to encapsulate a set of common
business functions. When capturing the outputs of business analysis using the Unified
Modeling Language (UML), Use Cases are identified. A Use Case documents a particular interaction sequence between a user and a system (a common example is withdrawing
money from an ATM). Such an interaction typically involves multiple, but related, steps.
The business logic associated with the steps from such a Use Case can typically be
housed in a session bean. There are various analogies for and examples of session beans
on Day 4 and Day 5, “Session EJBs.”
Session beans offer a synchronous interface through which the client can use the business logic. Session beans are not intended to hold essential business data. The session
bean will either hold no data on an ongoing basis, or the data it does hold will tend to be
temporary (only relevant to the current user session) rather than persistent. If a session
bean wants to obtain data from a database, it can use JDBC calls. Such a bean may provide part of solution when providing an e-commerce catalog as described earlier.
Alternatively, application data can be obtained through entity EJBs.
Components: Entity Beans
An entity EJB, again often just called an entity bean, is a representation of some business
data. During analysis, various business concepts will be discovered, such as “customer”
or “account.” These business “objects,” sometimes called “entities,” represent the core
data manipulated during the business processes. If such a business object contains
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 44
44
Day 2
dynamic data, has associated functionality, and can be shared between multiple clients at
any one time, this business object would probably map to an entity bean.
Entity beans offer a synchronous interface through which the client can access its data and
functionality. Entity beans will access underlying data sources (frequently, a database or
possibly an ERP system) to collect all the business information they represent. The entity
bean will then act as the dynamic representation of this business data—providing methods
to update and retrieve it in various ways. As you will see later, entity beans are frequently
used together with session beans to provide the business functionality of a system. Entity
beans are discussed further on Day 6, “Entity EJBs.”
A message-driven EJB, or just message-driven bean, fulfills a similar purpose to a session bean, but is asynchronous in nature. There are times when it is inefficient to interact
synchronously with a component. One example would be if you wanted to log the details
of a particular transaction to an underlying data store. In many cases, it is not important
that the logging is done immediately, just as long as it is done reliably. This type of operation can quickly become a performance bottleneck if performed synchronously. The
same is true of “undoable” operations, such as credit card processing.
Components: Message Beans
Message-driven beans offer an asynchronous interface through which clients can interact
with them. The bean is associated with a particular message queue, and any messages
arriving on that queue will be delivered to an instance of the message-driven bean. As
with session beans, message-driven beans are intended to house business logic rather
than data, so they will access any data required through JDBC or entity beans. To use the
services of a message-driven bean, a client will send a message to its associated message
queue. If a response is required, another message queue will typically be used. Messagedriven beans and the Java Message Service with which they interact are discussed further
on Day 9, “Java Message Service,” and Day 10, “Message-Driven Beans.”
The Presentation Tier
Given some business logic implemented as EJBs, you must provide clients with access to
this functionality. What is needed is some presentation logic coupled with a way of displaying information to the user. The presentation logic will govern which screens are displayed to the user in which order and how the presentation logic will interact with the
business logic in the business tier to work through the appropriate business process.
The way in which user input is received and information is displayed will depend on
the type of client. The client can range from a WAP phone through to a standalone
Java application with Swing interface. Each type of client will require different
04 0672323842 CH02
3/20/02
9:37 AM
Page 45
The J2EE Platform and Roles
45
presentation tier functionality to exchange and display information. The most common
type of client for a J2EE application is a Web client, in the shape of a Web browser. In
this case, the presentation tier will present a Web-based interface that produces HTML
and consumes HTML form-based input.
Components: Web-Centric
To create a Web-based user interface, you need to apply Web-centric components. J2EE
provides two types of Web-centric components—JavaServer Pages (JSP) and Java
servlets. These components are applied in the presentation tier and provide services to
clients that use HTTP as a means of communication. For example, Web-centric components can interact with clients such as the following:
• Standard HTML-oriented browsers, such as Microsoft Internet Explorer and
Netscape Navigator
• Java 2 Micro Edition (J2ME) enabled devices, connecting across a wireless network
• Desktop clients using raw HTTP or sockets functionality
• Wireless Markup Language (WML) browsers, such as those found on WAPenabled mobile phones
Figure 2.4 shows how you would typically use these components.
FIGURE 2.4
Typical use of Webcentric.components.
Web-centric
Client
HTTP
Request
J2EE Server
Web Container
HTTP
Response
JSP
or
Servlet
Presentation
Tier
EJB Container
Enterprise
Bean
Business
Tier
Figure 2.4 shows a Web-centric client making a request to either a servlet or JSP. The
server-side component parses the client’s request and then calls the EJB. The EJB contains the application’s business logic. When the servlet or JSP receives a response from
the EJB, it is responsible for presenting the data it receives. After the servlet or JSP has
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 46
46
Day 2
prepared the response, it passes it back to the client, completing the request-response
cycle.
The previous illustration demonstrated a model where the Web-centric component was
responsible for presenting data supplied by an EJB. The EJB was responsible for the
execution of the business logic. However, you do not have to use EJBs as part of a Webcentric solution. A simpler application can consist of pages of markup—for example,
HTML—and servlets, or JSPs, or a combination of JSPs and servlets.
The next section of today’s lesson shows you the relationship between JSPs and servlets,
so that you are in a position to choose which of these technologies best suit your needs.
JavaServer Pages (JSP)
JSPs allow you to dynamically create pages of markup, such as HTML, in response to a
client’s request. If you are familiar with other Internet technologies, you can liken JSP to
Active Server Pages (ASP) or ColdFusion. But be aware that these technologies are similar to JSP—not the same.
A JSP consists of a combination of JSP tags and scriplets, which contain the executable
code, and static markup, such as HTML or XML. The code contained in the JSP is identified and executed by the server, and the resulting page is delivered to the client. This
means that the embedded code can generate additional markup dynamically that is delivered to the client alongside the original static markup. The client sees none of this processing, just the result.
The JSP tags delimit sections of executable code and form the basis of any JSP page.
Scriplets are delimited sections of script that allow a JSP further processing power.
Typically, you can write this script using the Java programming language, but different
JSP implementations may support additional languages.
Day 13, “JavaServer Pages,” shows you in detail how to write JSPs and how JSPs interact with other J2EE components. To whet your appetite today will introduce you to how
JSPs work and interact with other J2EE components. Figure 2.5 shows a scenario that
depicts the typical interactions of client, JSP, and J2EE components.
Figure 2.5 shows a client making a HTTP request for a JSP. The first time a user makes a
request the JSP container handles it by converting the JSP into a Java source file and
compiling it. In most implementations, this file is a servlet. The servlet forwards the
request to a business logic component, such as another servlet, a JavaBean, or an
Enterprise JavaBean. The component performs some action, such as accesses a database
or processes the client’s input, and returns a response to the servlet. The servlet passes
this response to a JSP that generates the markup language that the client will display.
Finally, the JSP container and the Web Server return the markup to the client.
04 0672323842 CH02
3/20/02
9:37 AM
Page 47
The J2EE Platform and Roles
FIGURE 2.5
The interactions
between a client, JSP,
and J2EE component.
Web-centric
Client
HTTP
Request
47
J2EE Server
JSP Container
HTTP
Response
JSP
2
EJB Container
Enterprise
Bean
Database
As you can see, JSPs provide a very powerful method for dynamically creating pages of
markup. They also allow Web clients to indirectly access the application logic that other
J2EE components contain. Importantly, you have seen the relationship between a JSP
and a servlet.
Java Servlets
Servlets add processing power to servers that employ a response-request model. Perhaps
the most common of such servers is the Web server. In this instance in the past, CGI
scripts would provide this kind of functionality; now you can use servlets. Although
servlets are similar to CGI scripts, they are superior in many ways:
• Speed—You deploy servlets as Java class files. The class file consists of Java byte
codes, which means that they execute faster than interpreted scripting languages,
such as Perl.
• Platform Independence—Servlets are platform-independent classes, so they can be
run under different servers on different operating systems.
• Consistency—Servlets use a standard API (the Servlet API), so they enjoy the support of many Web servers.
• Power—Servlets can access any of the Java APIs. For example, a servlet can use
JDBC to access a data store, or access remote objects such as EJBs over RMI.
• Support—The Servlet API exposes a number of classes that greatly simplify many
of the tasks a server-side programmer must perform. For example, the Servlet API
provides direct access to response and request information (you do not have to
explicitly parse request data), and it provides support for state management through
a Session object and classes to manipulate cookies.
04 0672323842 CH02
3/20/02
9:37 AM
Page 48
48
Day 2
One of the greatest facets of the servlet is its ability to interact with other J2EE components. Servlets can interact with other servlets or EJBs in just the same way as a JSP.
For example, Figure 2.6 shows a client calling a servlet and then that servlet accessing
an EJB.
FIGURE 2.6
The interactions
between a client,
servlet, and EJB.
Web Browser
HTML Form
J2EE Server
HTTP
POST
Servlet Container
Servlet
HTTP
Response
HTML Page
EJB Container
Enterprise
Bean
Figure 2.6 shows a user completing an HTML form and then initiating a HTTP POST to a
servlet on a Web server. The Servlet Container on the Web Server invokes the servlet and
passes it an object that represents the client’s request. The servlet calls on the services of
the EJB to perform the applications logic. Finally, the servlet generates an HTML
response, which it returns to the client via a response object. In this instance, the servlet
used an EJB, but it could also perform the processing itself or call another servlet or
JavaBean.
Evaluation of Web-Centric Components
Now that you’ve been introduced to JSPs and servlets, you may wonder which component best suits a given scenario. In many instances, you can use JSPs and servlets interchangeably—remember that a servlet underlies a JSP. Both components dynamically create markup, operate on a request-response model, and can interact with other J2EE components.
However, Sun Microsystems provide guidance to help you develop applications using
Web-centric components. This guidance takes the form of BluePrints that offer guidelines on the best practice and recommended use of J2EE technologies. The following
guidelines derive from these BluePrints.
Generally, the presentation tier should use JSP pages that are presentation-centric, making them ideally suited to the generation of markup. In addition, JSP pages consist of
04 0672323842 CH02
3/20/02
9:37 AM
Page 49
The J2EE Platform and Roles
49
XML tags, which are familiar to Web content providers. This familiarity allows content
providers to easily maintain a site’s content without altering code. Conversely, you
should consider servlets as a programmatic tool that you don’t modify frequently. There
are two main instances when you should use servlet in preference to JSPs:
• Generating Binary Data—You should use servlets to output binary data, such as
images.
• Extending Web Server Functionality—For example, you could use a servlet to do
the filtering of mail for a mail service.
As a guide, you should use JSPs in preference to servlets unless you require one of the
previously mentioned items of servlet functionality.
The Client Tier
J2EE supports a wide range of clients. These clients range from thin clients, such as a
Web browser, to intelligent clients, such as mobile devices that run J2ME Midlets. Both
of these clients communicate through HTTP, but other clients may use SOAP, sockets, or
even CORBA. The factors that unite these disparate clients are that they all call and subsequently receive a response from J2EE middle tier components. This part of the day
looks at a range of J2EE clients and focuses on which components interact with them.
HTTP Clients
Java technologies have a long history of providing a wide range of services to clients that
communicate via HTTP. For example, is it possible that someone has never heard of
applets? J2EE provides services to Web-based clients by using two components—JSPs
and servlets. These technologies may not be as familiar to you as the applet. Previously,
this chapter explained what these components are; now, it explains how they integrate
with HTTP clients.
Static HTML Client
It’s hard to imagine a Web application that relies on static HTML pages, but, believe it or
not, some do. Typically, sites that use static pages are small and require very little functionality. The type of functionality they do require includes the processing of user
response forms, basic e-commerce capabilities, and automated navigation. In a nonenterprise environment, a developer can use a CGI script to provide this functionality.
With J2EE, you can use a servlet. For example, consider a customer feedback form written in HTML.
Figure 2.7 shows that the user completes the HTML Form and then initiates a HTTP
POST to the servlet. The servlet parses the POST data; at this point, it could pass the
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 50
50
Day 2
data to another component. However, in this instance, it simply uses classes in the
JavaMail API (see Day 11, “JavaMail”) to send the customer an e-mail response.
FIGURE 2.7
Using a servlet to
process an HTML
form.
J2EE Server
Web Browser
HTTP
(POST)
HTML
Form
JSP Container
Request
JSP
HTTP
Response
HTML
Page
E-mail
E-mail Client
JavaMail
Message
Dynamic HTML Client
Many Web applications use HTML pages, which they generate when they receive a
client request. These dynamic pages typically contain information that is context sensitive. For example, the page may contain a simple time stamp, a banner advertisement, or
information retrieved from a database. With J2EE, you can use a JSP to create dynamic
HTML pages. Figure 2.8 shows how JSPs relate to a Web client.
FIGURE 2.8
A Web-client interacting with a JSP.
Web Browser
HTTP
Request
for
index.jsp
J2EE Server
JSP Container
HTTP
Response
HTML
Page
JSP
EJB Container
Enterprise
Bean
04 0672323842 CH02
3/20/02
9:37 AM
Page 51
The J2EE Platform and Roles
51
The client makes a HTTP request for the page index.jsp. The JSP engine (on the server) interprets the tags within the JSP, calls the EJB, the EJB returns a response, and then
the engine returns a page of HTML to the client. The functionality in this model is
encapsulated in the EJB.
Java Applet Client
In case you do not know, an applet is a small GUI-based program that typically executes
within the context of a Web browser. In the sphere of the Internet, a client requests an
HTML page that references a server-side Java class file (the applet). The Web server
responds to the client by returning this file. The applet then executes within the Java
Virtual Machine (JVM) the client browser supplies.
In some respects, applets are an ideal way of providing remote access to J2EE applications. They are highly portable, run in an environment with strict security controls (the
Java 1.0 Sandbox as a minimum), enjoy wide industry support (for example, Netscape
and Internet Explorer browsers), and offer a rich GUI. However, browsers do not keep
pace with changing specifications; so many browsers only support the deprecated Java
1.0 event model. This may cause you a number of problems, especially when working
with AWT, which underwent a major change in its event model between Java 1.0 and
Java 1.1. One of the best ways you can control the presentation tier is to deploy applets
within a controlled network, such as a corporate intranet. In this instance, you can write
code to work to the limitations of known browsers rather than working to the lowest
common denominator.
Other HTTP Clients
The three previous clients are very desktop browser centric, but there are many other
types of clients that communicate by using HTTP. These clients include mobile devices,
such as cellular phones, smart phones, and PDAs. For example, Figure 2.9 shows a WAP
device calling a JSP. The component-based architecture means that the only change is to
the JSP because WAP devices don’t display HTML. All of the logic in business tier
remains unaffected.
You can also write an application that uses HTTP to communicate and still communicate
with the Web-centric J2EE components.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 52
52
Day 2
FIGURE 2.9
A WAP device with a
WML browser interacting with a JSP.
WAP Device
with
WML Browser
HTTP
Request
J2EE Server
JSP Container
HTTP
Response
WML
Page
JSP
Presentation
Tier
EJB Container
Enterprise
Bean
Business
Tier
Standalone Client
The HTTP clients all used a model that in essence was client—presentation tier—
business tier—integration tier. However, there are other clients that may assume the
responsibilities inherent in some of these tiers. For example, an application can connect
to an EJB via its container, rather than route through a JSP in the presentation tier. Figure
2.10 shows such an example.
FIGURE 2.10
A standalone client
directly interacting
with an EJB.
Standalone
Client
J2EE Server
Enterprise
Bean
EJB Container
Figure 2.10 shows a standalone client accessing an EJB via its container. In this example,
the client would typically be another EJB. Because the client accesses the business tier, it
becomes responsible for the provision of the presentation tier. Consequently, in this
example, the client EJB can pass the data to a servlet, which will then generate the
appropriate response to its client.
04 0672323842 CH02
3/20/02
9:37 AM
Page 53
The J2EE Platform and Roles
53
Figure 2.11 shows another scenario where the standalone client bypasses both the presentation tier and business tier to access enterprise information system resources directly.
Typically, the client uses JDBC to access the resources.
FIGURE 2.11
A standalone client
interacting directly
with an EIS resource.
Client
Application
Enterprise
Information
Service
In this scenario, the client takes responsibility for the presentation tier and business tier.
Interestingly, the client may not be another J2EE client: it might be a single application
that encapsulates all presentation and business logic. If this is the case, the client will not
gain the benefits of enterprise computing, especially scalability, because it is effectively
working on the 2-tier client-server model.
Business to Business
The previous clients and scenarios worked on the premise of connecting to a component
in a different tier—with the exception of the EJB to EJB example. However, many components do connect to components within the same level tier as themselves. For example,
consider a small garage where mechanics order parts from a local catalogue via a handheld device. Figure 2.12 shows the architecture for the system. As you can see, a JSP
handles their orders by accessing a catalogue via JDBC. With each order for a part, the
JSP decrements the count for that part. When the part count reaches zero (Just-in-Time
stock management), the JSP connects to a JSP at the parts wholesaler and places an
order for more parts.
The previous example showed two JSPs communicating, but this could just as easily be
two servlets or two EJBs. Another interesting development in peer-to-peer communications such as these is the use of messaging. For instance, the previous example requires
both JSPs to be available at the same time to complete an order. However, using the messaging facilities JMS provides, you may create a system where both JSPs can asynchronously send and receive orders. You can learn a little more about JMS later today and a
lot more on Day 9, “Java Message Service.”
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 54
54
Day 2
FIGURE 2.12
JSP to JSP interaction.
Mechanic’s
Handheld
Device
J2EE Server
J2EE Server
JSP Container
JSP Container
JSP
JSP
EJB Container
Enterprise
Bean
Catalogue
Database
Non-Java Clients
It is possible to use non-Java clients to access J2EE applications and application components. One obvious solution is that non-Java clients can use HTTP to access the services
offered by a servlet or JSP. This is particularly attractive when the servlet or JSP produces and consumes data-oriented information in XML rather than HTML. This could be
done in the context of exposing the component as a Web Service, which is discussed in
the next section of today’s lesson.
In the case of an EJB, things are a little trickier. However, the RMI-IIOP protocol used to
communicate with EJBs allows them to interoperate with clients written using the
Common Object Request Broker (CORBA) standard. Hence, it is possible to expose an
EJB as a CORBA server that can be used by CORBA clients written in C++ or even
COBOL.
Another alternative is that EJBs can be accessed from a Microsoft COM client using the
J2EE Client Access Services (CAS) COM Bridge or Table Bridge.
Web Services
Web Services are XML-based middleware components that applications access over
HTTP and SOAP. They enjoy industry-wide support and are not a proprietary solution. In
fact, because Web Services use XML and open communication standards, any client that
04 0672323842 CH02
3/20/02
9:37 AM
Page 55
The J2EE Platform and Roles
55
can understand SOAP messages can consume Web Services. J2EE provides a rich framework that facilitates the building, deployment, and consumption of Web Services. You
can learn more about J2EE and Web Services on Days 20 and 21.
Understanding Containers
When you previously installed J2EE, you also installed Sun Microsystems’ reference
implementation of J2EE, which is a J2EE Product. All J2EE products must provide containers to house J2EE components. The role of the container is to provide a component
with the resources it needs to operate and a runtime within which to execute; yet still
provide a degree of protection (security) to the application host.
Containers provide a number of services for a component. These services include lifecycle management, threading, security, deployment, and communication with other components. In addition to these services, a container must provide components with Java compatible runtime that conforms to J2SE 1.3.
Different components perform different tasks, so it may come as no surprise that they
require different containers. A Product Provider can supply any of the following containers:
• Applet container
• Application client container
• JSP container
• Servlet container
• Web container
• EJB container
It was mentioned previously that all containers must supply a J2SE 1.3-compatible runtime environment. Interestingly, many applet hosts (typically Web browsers) do not support such a high runtime version. To compensate for this, the J2EE specification states
that an applet container can use a Java plug-in to provide an appropriate environment.
All of these containers must provide the components they house with certain services and
communications protocols. You will learn more about these service and protocols later in
this chapter in the “Understanding the Service Supply to their Components” section.
These containers must all also provide access to certain J2EE APIs; Table 2.2 shows
these APIs.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 56
56
Day 2
TABLE 2.2
J2EE Required Standard Extension APIs
API
Applet
Application Client
Web
EJB
JDBC 2.0 Extension
N
Y
Y
Y
JTA 1.0
N
N
Y
Y
JNDI 1.2
N
Y
Y
Y
Servlet 2.2
N
N
Y
N
JSP 1.1
N
N
Y
N
EJB 1.1
N
Y
Y
Y
RMI-IIOP 1.0
N
Y
Y
Y
JMS 1.0
N
Y
Y
Y
JavaMail 1.1
N
N
Y
Y
JAF 1.0
N
N
Y
Y
For the sake of simplicity, Table 2.2 does not distinguish between servlet, JSP, and Web
containers. You can consider these three containers as a stack where each builds on the
functionality of the other. At the bottom of the stack is the servlet container, which must
support HTTP; optionally, it might support other protocols. Above this is the JSP container, which provides the same functions as the servlet container and an engine to build
servlets from JSP pages. Finally, the Web container provides all the services of the JSP
container and access to J2EE service and communications APIs, which today’s lesson
details next.
Understanding the Services Containers
Supply to Components
Previously, you learned that there are a variety of J2EE containers, and that these containers house J2EE components and provide methods and protocols that allow components to communicate with each other and with platform services. You also learned that a
J2EE server underlies the container. This server is often known as the J2EE Product;
there are other possible J2EE products that you will learn about later today in the “J2EE
Product Provider” section of today’s lesson.
J2EE products must provide components with certain standard services. Yesterday you
were introduced to some of these services; today you will explore them in a little more
detail and see how they work in conjunction with J2EE components.
04 0672323842 CH02
3/20/02
9:37 AM
Page 57
The J2EE Platform and Roles
57
Hypertext Transfer Protocol (HTTP)
A W3C specification defines HTTP 1.0, which is a protocol that allows the exchange of
data of various formats in a widely distributed network. Both JSPs and servlets allow
clients to access a J2EE application through the use of HTTP 1.0. Clients that have a
Java runtime communicate with J2EE applications by using the java.net package,
which is part of J2SE.
2
Note
The J2EE Specification states that containers need only provide support for
HTTP 1.0. However, in practice, the majority of containers, including those in
the RI, support HTTP 1.1.
HTTP over Secure Sockets Layer (HTTPS)
A Netscape specification defines SSL 3.0, which is a protocol that manages the secure
transfer of data over a network. HTTPS uses SSL 3.0 as a sub-layer to HTTP to provide
secure data transfer over the Internet. In common with HTTP, the JSP and Java Servlet
APIs define the server-side API, and java.net defines the client-side.
Java Database Connectivity (JDBC)
JDBC is an API that allows you to access any tabular data source including relational
databases, spreadsheets, and text files. The API allows you to connect to a database via a
driver and then execute Structured Query Language (SQL) statements against that database. Appendix B, “SQL Reference,” provides an SQL reference. The API consists of
two packages—java.sql (ships with J2SE) and javax.sql (ships with J2EE). The
javax.sql package provides many of the features an enterprise application requires,
such as transaction support and connection pooling.
Day 8, “Transactions and Persistence,” describes JDBC, so today’s lesson provides only
a quick overview of JDBC architecture. Typically, an EJB uses the API, but any other
component can use it, for example a servlet. To connect to a given database, you must
load a JDBC driver for that database. However, in the case of an Open Database
Connectivity (ODBC) data source, you may optionally use a JDBC-ODBC bridge where
no driver exists. After the appropriate driver loads, you can make a connection to the
database and then create and execute SQL statements. If the statement is a query, a
ResultSet is returned, which contains the results of the query. You can then manipulate
these query results.
04 0672323842 CH02
3/20/02
58
9:37 AM
Page 58
Day 2
Java Transaction API (JTA)
A transaction is an atomic group of operations. For example, a banking application may
debit one account and credit another. The transaction is considered complete when both
the debit and credit are complete. If one operation fails, the other must roll back. A distributed system makes transaction management complex. In such a system, a transaction
manager must coordinate transactions across the system.
The JTA API allows you to work with transactions independently of the transaction
manager. You work directly with the methods JTA exposes via an instance of
UserTransaction. In a simple scenario, you can use the begin(), commit() and
rollback() methods—which might be familiar SQL commands if you are a database
programmer—to manage the transaction. Day 8 explores JTA and its use with JDBC.
Java Authentication and Authorization Service (JAAS)
Anyone who has an interest in security knows that Java technologies have a rich history
of providing a strong security framework. JAAS is a new supplement to this existing
security framework. It provides both authorization and authentication services that the
Pluggable Authentication Module (PAM) provides. In common with the Java 2 security
framework, JAAS provides access control based on code location and code signers. In
addition, JAAS provides access control to a specific user or group of users.
JAAS allows you to simply swap at runtime between encryption algorithms when
authenticating users. This is because you interact with JAAS through a login context, so
you effectively work with an abstraction of the authentication mechanisms.
Note
Because JAAS does not actually contain classes that encrypt data, it is not
subject to U.S. export control restrictions. This means that developers outside of the U.S. are free to download JAAS.
JAAS is an optional package to J2SE 1.3.x, and it ships with sample authentication modules that use JNDI, Solaris, and Windows NT. You can download the current version of
JAAS from http://java.sun.com/products/jaas/. You can learn more about using
JAAS with JNDI and J2EE components on Day 15, “Security.”
Java API for XML Parsing (JAXP)
XML is a text-based markup language that describes data. It provides a platformindependent and language-independent method for exchanging data between applications.
04 0672323842 CH02
3/20/02
9:37 AM
Page 59
The J2EE Platform and Roles
59
Because XML consists of plain text, it is human-readable. However, you very rarely read
XML: you use an application that implements an XML API to read the data.
The JAXP API allows you to parse XML documents using the Document Object Model
(DOM) or the Simple API for XML (SAX). One very useful feature of JAXP is that you
can swap between XML parsers without making changes to your code. For example, if
speed suddenly becomes very important, you can use the SAX parser because it reads a
very large document in a fraction of the time of the DOM parser. Another useful feature
of JAXP is that it provides support for Extensible Stylesheet Language Transformations
(XSLT). For example, the J2EE Reference Implementation (RI) provides a transformation engine that supports XSLT. This allows you to dynamically transform an XML document into either another XML document, HTML, or plain text.
There are many ways that you might use XML within a J2EE application. For example,
you can store content in XML and then transform the XML using XSLT so that a JSP
can render content to devices that support different markup languages. Another typical
use of XML is in the arena of business-to-business applications, where organizations can
exchange data independently of their system architectures. Finally, one very important
use of XML is within Web Services, which you will learn about on Days 20 and 21.
Java Naming and Directory Interface (JNDI)
JNDI provides an API for working with naming and directory services. A naming service
simply associates names with objects, for example, the Domain Name System (DNS). A
directory service also associates names with objects, but it also provides additional information through attributes, for example, a Lightweight Directory Access Protocol (LDAP)
directory.
Although JNDI provides access to a wide array of naming and directory services, each
service must provide a Service Provider. This is similar to JDBC and drivers, but in this
instance, it is a naming or directory service and a Service Provider. For example, an
LDAP directory must provide an LDAP Service Provider, which JNDI hides from you as
a developer.
Whenever you need to access naming or directory services, you can use JNDI. More
specifically, you use JNDI in J2EE in three main instances—to access or register EJBs or
objects in an RMI registry or to access the CORBA Common Object Services (COS)
naming service. You can learn more about JNDI in Day 4.
JavaBeans Activation Framework (JAF)
Typically, you use JAF in the context of JavaBeans (that’s JavaBeans, not Enterprise
JavaBeans!). However, a J2EE product must provide JAF for the JavaMail API to use
2
04 0672323842 CH02
3/20/02
9:37 AM
60
Page 60
Day 2
MIME types. JAF allows you to send e-mails that are not simply plain text. Instead, you
can use different MIME types or send attachments. You can learn more about the use of
JAF in the context of JavaMail on Day 11.
JavaMail
The JavaMail API provides classes that allow you to work with e-mail. Specifically, it
allows you to send and receive e-mails by using a wide variety of protocols, including
POP, SMTP, and IMAP. You can create e-mails that conform to a large number of MIME
types, because the API uses JAF to provide support for a number of MIME types. For
example, you can create HTML messages that contain embedded graphics and even have
attachments.
Most Internet applications require the ability to send e-mail messages. You can use this
API together with JAF to send e-mails from a JSP, a servlet, or an EJB. You can learn
more about the API on Day 11.
Java Message Service (JMS)
Messaging is the process of communication between applications or components; it does
not include application to human communications, such as e-mail. The JMS API allows
you to create, read, and store messages.
JMS support two messaging models—point-to-point and publish-subscribe. Point-topoint messaging is where an application sends a message to a queue (a prearranged destination for messages), and then a client application collects that message. For example,
leaving a voicemail message is a real-world example of this model. The publishsubscribe model requires client applications to subscribe to a topic with a message broker that again acts as a prearranged message destination. When an application sends a
message to the message broker, the message broker immediately forwards the message to
all current subscribers.
You can send and receive JMS messages by using both session and entity beans.
However, you can only do this synchronously; the sender must suspend execution until
the receiver receives the message. Alternatively, you can use a message-driven bean to
send messages asynchronously. You can learn more about message-driven beans on Day
10 and JSM on Day 9.
Java Interface Definition Language (Java IDL)
Java IDL provides a way for you to access and deploy remote objects that comply with
the Common Object Request Broker Architecture (CORBA) defined by the Object
Management Group (OMG). CORBA Interface Definition Language (IDL) provides a
04 0672323842 CH02
3/20/02
9:37 AM
Page 61
The J2EE Platform and Roles
61
language-independent means of defining object interfaces. OMG provides mappings
between various languages and IDL. A client written in any language that has an IDL
binding can access objects you export by using CORBA. For example, a Java client
can consume objects written in other languages, such as C++, C, Smalltalk, COBOL,
and Ada.
In terms of J2EE applications, you can look up CORBA remote objects in the COS naming service through JNDI. The main reason you would want to use Java IDL, as a Java
developer, is to allow your J2EE application to access legacy systems. For example, you
might have a legacy Integration tier where a COBOL application manages data access.
Java IDL allows an EJB in the business tier to communicate with the COBOL object,
thus negating the need to rewrite the entire backend legacy code.
Remote Method Invocation over Internet Inter-Orb
Protocol (RMI-IIOP)
RMI is a distributed object system that allows Java objects to interact with other Java
objects running in a different virtual machine (usually on a remote host). In practice, you
can access these remote objects almost as if they were local; you simply get a reference
to the remote object and invoke its methods as if it were running in the same virtual
machine. The seamless nature of this access is due, in part, to the fact that RMI is a Javaonly distributed object system that relies on a proprietary transport protocol, namely,
Java Remote Method Protocol (JRMP), to exchange information between client and server. However, this means that you can only use it to access other Java objects—not nonJava objects.
The actual process of performing a remote method invocation is similar to that of
CORBA, namely, RMI utilizes client-side stubs and server-side skeletons. A client
invokes a remote method by making a request on the stub, and this forwards to the server
where the skeleton converts the request into an actual method call on the remote object.
Any arguments for the remote method are marshaled by the stub into a serialized form
before they are forwarded to the skeleton, which, in turn, unmarshalls the arguments.
This marshalling allows objects to transport across a network.
Unfortunately, this protocol is not suitable for the type of enterprise-level interactions
required by EJBs, where transaction and security context must be propagated across
remote method calls. To this end, Sun Microsystems created a new implementation of
RMI called RMI-IIOP. This keeps the same semantics as RMI (remote interfaces, passing
serialized objects, and so on) but uses the CORBA Internet Inter-ORB Protocol (IIOP) as
its transport mechanism. IIOP already contains all of the necessary hooks to propagate
security and transaction context, so this new protocol can act as the core transport for
EJBs in the J2EE architecture.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 62
62
Day 2
RMI-IIOP is used by default as the transport mechanism when generating stubs and
skeletons for EJBs. You can also explicitly create RMI-IIOP clients and servers for your
own applications by using flags on the RMI compiler (rmic) to create RMI-IIOP stubs
and skeletons rather than the default JRMP stubs and skeletons.
You can also use RMI-IIOP as a mechanism for exposing your EJB components to
CORBA clients without having to learn IDL. You can specify a flag to the RMI compiler
that gets it to generate CORBA IDL on your behalf. After you have the CORBA IDL,
this can be used together with an alternative language binding to create a client for the
EJB that is written in another language.
Connector Architecture
The J2EE Connector Architecture allows your J2EE application to interact with
Enterprise Information Systems (EIS), such as mainframe transaction processing,
Enterprise Resource Planning (ERP) systems, and legacy non-Java applications. It does
this by allowing EIS vendors to produce a resource adapter that product providers can
plug into their application servers. The J2EE developer can then obtain connections to
these EIS resources in a similar way to obtaining a JDBC connection.
The J2EE Connector Architecture defines a set of contracts that govern the relationship
between the EIS and the application server. These contracts determine the interaction
between server and EIS in terms of the management of connections, transactions, and
security. You can learn more about Connector Architecture on Day 19, “Integrating with
External Resources.”
Introducing Platform Roles
To create, package, and deploy any J2EE application—other than the simplest
application—requires the effort of more than one person or organization. For example, in
the development arena, a team of developers will write the J2EE components and someone else will assemble the finished application. In the production environment, someone
will configure the J2EE environment and deploy the application, and yet another person
will monitor the running application and its physical environment. In smaller organizations, there may be no physical distinction between these roles, but they will still be logically separate. Based on this premise, it is no surprise that Sun Microsystems suggest a
named team whose responsibility it is to perform these tasks.
This team, together with Product Providers and Tool Providers constitute the J2EE platform roles. It is these roles that this section explores.
04 0672323842 CH02
3/20/02
9:37 AM
Page 63
The J2EE Platform and Roles
63
J2EE Product Provider
A J2EE product must include the component containers and J2EE APIs the J2EE specification states; today’s lesson has introduced all of these containers and APIs. Examples of
J2EE products include operating systems, database systems, application servers, and Web
servers. An organization that supplies a J2EE product is known as the J2EE Product
Provider.
The J2EE Product Provider is also responsible for mapping application components to
the network protocols the J2EE specification defines. In addition, the Product Provider
must provide deployment tools for the Deployer and management tools for the System
Administrator. The end of this section provides an explanation of these tools.
The Product Provider is free to provide implementation-specific interfaces that the J2EE
specification does not define. Hence, you will occasionally see a warning in a lesson that
highlights a vendor-specific piece of functionality.
Application Component Provider
As you have already seen, a J2EE application consists of components, but it also may
consist of other resources, such as HTML files or XML files. The Application
Component Provider creates both these resources and components. Almost all organizations will use several component providers. They may exist in-house, or the organization
may outsource component creation or buy in components. Whichever is the case, specialists in the different tiers (presentation, business, and data access) will write the components that relate to that tier. For example, a business tier specialist will write EJBs,
whereas a presentation tier expert may write JSPs. Regardless of the Application
Component Provider’s specialist area, the Tools Provider will supply them with tools to
write components.
Application Assembler
After the Application Component Providers write an application, the Application
Assembler assembles the application into a J2EE application. The Application Assembler
packages the application into an EAR file that must conform to the J2EE specification.
Other than assembly, the Application Assembler is responsible for providing instructions
that state the external dependencies of the application. Typically, the Application
Assembler uses tools the Tools Provider or Product Provider provides to perform these
tasks.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 64
64
Day 2
Application Deployer
The Application Deployer is the first person who requires knowledge of the production
environment. This is because he or she must deploy the application into that environment. Specifically, the Application Deployer must install, configure, and start the execution of the application. Typically, the Product Provider provides tools that help perform
these tasks.
The installation process is where the Application Deployer moves the application to
the server and installs any classes the container requires to perform its duties. During the
configuration process, the Application Deployer satisfies any external dependencies the
Application Assembler stipulates and configures any local security settings, for example,
modifies a policy file. The final stage, starting execution, is where the Application
Deployer starts the application in readiness to service clients.
Systems Administrator
The Systems Administrator configures and maintains the enterprise network, and monitors and maintains the application the Application Deployer deployed. The Product
Provider supplies tools that assist the Systems Administrator in the monitoring and maintenance of the application.
This concludes the list of platform roles that specifically work with the application; only
the Tool Provider remains. Figure 2.13 shows the interactions between each of these
roles and their interactions with the J2EE application.
FIGURE 2.13
Product Provider
J2EE roles.
Development Environment
Application
Component
Provider
Application
Assembler
Production Environment
Application
Deployer
Tools Provider
System
Administrator
04 0672323842 CH02
3/20/02
9:37 AM
Page 65
The J2EE Platform and Roles
65
Tool Provider
As you have seen, many of the platform roles use tools the Tool Provider supplies. These
tools assist people with the creation, packaging, deployment, and maintenance of J2EE
applications. Currently, the J2EE specification only defines that the Product Provider
must supply deployment and maintenance tools; it does not stipulate what these tools
should be. Future releases of the specification are likely to provide further guidelines, so
that Tool Providers can supply platform-independent standardized tools sets.
To offer you a typical overview of the tools that Tools Providers and Product Providers
supply, this section concludes with a brief survey of the tools that ship with the J2EE reference implementation. For use guidance, please refer to the J2EE documentation.
• J2EE Administration Tool—Enables the addition and removal of resources, such as
JDBC drivers and data sources.
• Cleanup Tool—Removes all J2EE applications from the server.
• Cloudscape Server—Starts and stops the Cloudscape relational database.
• Deployment Tool—Enables you to package and deploy J2EE applications.
• J2EE Server—Launches and stops the J2EE server.
• Key Tool—Enables you to generate public and private keys and X.509 certificates.
• Packager—Allows you to package J2EE applications if you are not packaging
them using the deployment tool (see above). You can create EJB JAR, Web WAR,
Application Client JAR, J2EE EAR, and Resource Adapter RAR files.
• Realm Tool—Allows the administration of J2EE users and also the import certificates.
• Runclient script—Enables you to run a J2EE application client.
• Verifier—Verifies the integrity of EAR, WAR, and JAR files.
Future of J2EE tools
There are 3 Java Specification Requests underway that will affect the future of J2EE
tools:
• JSR 77—A new management model for tools that will provide a single management tool to configure the J2EE platform. You can read more about the JSR at
http://www.jcp.org/jsr/detail/077.jsp.
• JSR 88—A description of the APIs that enable the deployment tool. You can read
more about the JSR at http://www.jcp.org/jsr/detail/088.jsp.
2
04 0672323842 CH02
66
3/20/02
9:37 AM
Page 66
Day 2
• JSR 127—This defines the architecture that simplifies the creation and maintenance of Java Server application graphical user interfaces. You can read more about
the JSR at http://www.jcp.org/jsr/detail/127.jsp.
Packaging and Deploying J2EE Applications
The installation of a typical desktop application, such as a word processor, is usually a
straightforward affair. The installation program will ask you a few questions about the
functionality you require and where it should install its files. It will also examine parts of
your desktop machine (such as the Windows registry) to discover whether any components that it relies on are already installed. Normally, such an installation takes in the
order of a few minutes—half an hour at most.
The installation of a distributed enterprise application is unlike that of a packaged desktop application. The installation of a desktop application is reasonably straightforward
because
• The concept of word processing is well understood by most people, so they can
make an appropriate judgment on whether they need particular parts of the package
or not. There is little in the way of personal tailoring involved.
• All of the installation takes place on a single machine. The installation program
knows where to find existing configuration information and where it should install
the different parts of the application.
Compared with this, a distributed enterprise application will require a lot more information about the environment in which it is to be installed. This includes, but is not limited
to, the following:
• The location of the servers on which the server-side components will be deployed.
The Web and business components for an application could be distributed across
multiple servers.
• The appropriate level of security must be enforced for the application. The application must carry with it information about the security roles it expects and the
access each role has to the functionality of the application. These security roles
must be mapped onto the underlying security principals used in the distributed
environment.
• Components that access data and other resources must be configured to use appropriate local data sources.
04 0672323842 CH02
3/20/02
9:37 AM
Page 67
The J2EE Platform and Roles
67
• The names of components and resources must be checked and potentially changed
to avoid clashes with existing applications or to conform to a company-wide naming standard.
• Web-components must be configured so that they integrate with any existing Web
sites of which they will form a part.
As you can see, this is currently a far more specialist task, requiring knowledge about the
application and the environment in which it is being deployed. The application must
carry with it information about the requirements it has of the environment. The application assembler defines these requirements when the application is created. The application Deployer must examine these requirements and map them onto the underlying environment.
J2EE applications
A J2EE application will consist of the following:
• Zero or more Web components packaged as Web Archives (WAR files)
• Zero or more EJB components packaged as EJB-JAR files
• Zero or more client components packaged as JAR files
• Zero or more connectors packaged as Resource Archives (RAR files)
Naturally, there must be at least one component for there to be an application! The components that constitute an application must be packaged together so that they can be
transported and then deployed. To this end, all of the components in a J2EE application
are stored in a particular type of JAR file called an Enterprise Application Archive or
EAR.
Given the previous scenario, it should be clear that a J2EE application needs to carry
with it information about how its different parts interrelate and its requirements of the
environment in which it will be deployed. This information is carried in a series of XML
documents called deployment descriptors. There is an overall application deployment
descriptor that defines application-level requirements. This application deployment
descriptor is also stored in the EAR file.
Each individual component will have its own deployment descriptor that defines its own
configuration and requirements. These component deployment descriptors are carried in
the individual component archives described in the “Breaking Modules Down into
Components” section of today’s lesson. Figure 2.14 shows the structure of an EAR file
and how the application deployment descriptor, the component archives, and the component deployment descriptors fit within this structure.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 68
68
Day 2
FIGURE 2.14
Structure of an
Enterprise Archive
(EAR).
EAR
Application DD
EJB-JAR
EJB-JAR
EJB-JAR
DD
DD
DD
WAR
WAR
WAR
DD
DD
DD
Container DD
EJB
Modules
Web
Modules
JAR
JAR
JAR
DD
DD
DD
Client
Modules
RAR
RAR
RAR
DD
DD
DD
Resource
Modules
The application deployment descriptor contains application-wide deployment information and can potentially supersede information in individual component deployment
descriptors.
Note
The EAR file can also contain a container-specific deployment descriptor that
holds information that is useful to the container but falls outside the scope
of the J2EE application deployment descriptor.
The application is split into modules, each of which represents a component. If necessary, a module can contain an additional deployment descriptor to override some or all
settings in the deployment descriptor provided in the component archive file.
Breaking Modules down into Components
As you can see from Figure 2.14, components are represented in an EAR file by component archive files. Each module will point to its associated component archive file. Each
type of component archive file is a JAR-format file that contains the component’s classes
and resources together with a component-specific deployment descriptor.
04 0672323842 CH02
3/20/02
9:37 AM
Page 69
The J2EE Platform and Roles
69
The two most common types of component archive are EJB-JAR files and WAR files.
EJB Component
An EJB-JAR file contains all of the classes that make up an EJB. It also contains any
resource files required by the EJB. The properties of the component are described in its
associated deployment descriptor, called ejb-jar.xml, which is also included in the
EJB-JAR file.
The deployment descriptor described the main class files contained in the EJB-JAR file.
The deployment descriptor specifies which external resources are required by the component. It also contains extra information about the security and transaction settings. This
resource and extra information is often referred to as metadata. Figure 2.15 shows a subset of the contents of an EJB deployment descriptor.
FIGURE 2.15
An EJB deployment
descriptor indicates
the main classes in the
EJB-JAR file together
with the component’s
metadata.
Deployment
Descriptor (XML)
AgencyHome.class
Agency.class
specifies
AgencyBean.class
Home interface
Remote interface
EJB implementation
Security requirements
Transaction requirements
<security-role>
<role-name>admin</role-name>
</security-role>
<container-transaction>
…
<trans-attribute>Required</trans-attribute>
</container-transaction>
All of the component’s metadata can be altered or replaced by the application assembler
as they bind the component into the application. The application Deployer can also customize some of the metadata.
Note
An EJB-JAR file can contain more than one EJB.
EJB-JAR files and their deployment descriptors are discussed in more detail on Days 4
and 5. Other aspects of EJB deployment information, such as security and transactions,
are covered later.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 70
70
Day 2
Web Component
Servlets and JSPs can also be packaged together into a component archive file. The
archive file is a JAR-format file that contains the class files, JSP files, and resources
required by the Web component. In this case, the resources can include static HTML files
that form part of the application. This Web Archive (WAR) file also contains a deployment descriptor that indicates the Web components contained in the WAR.
Just as with the EJB deployment descriptor, the WAR deployment descriptor indicates
the main classes in the WAR file and the resources required by the components.
However, the WAR deployment descriptor also contains Web-specific information, such
as the URLs onto which servlets and JSPs should be mapped, and which is the front page
of the application.
WAR files and their deployment descriptors are discussed in more detail on Day 12,
“Servlets,” and Day 13. Other aspects of WAR deployment information, such as security,
are covered later.
Summary
Today, you looked in more detail at J2EE and the facilities that it provides. You saw how
the different J2EE technologies fit into the 3-tier model, and how it provides component
frameworks for different types of functionality.
You have seen that the EJBs provide a robust, scalable home for business logic and that
servlets (and JSPs) provide a flexible way of delivering application functionality to
clients. There are many different types of client, ranging from simple, markup-based
clients that work over HTTP to sophisticated and powerful clients that use GUIs and
RPC.
You have seen that the creation and deployment of an enterprise application requires
many different roles. You have also seen that an enterprise application is assembled from
many different parts, and that it must carry with it information about how all of those
parts fit together.
Q&A
Q Can a J2EE application be written without using any Enterprise JavaBeans?
A Certainly. You can write a client application that connects to a servlet in a Web
container and have that servlet connect directly to a back-end database. You don’t
04 0672323842 CH02
3/20/02
9:37 AM
Page 71
The J2EE Platform and Roles
71
need to add an EJB. An EJB can add value by providing persistent conversational
state if that is required. It can also provide transactional security and roll back to a
previous state should there be an interruption in the flow of data for any reason.
Therefore, you can use servlets and JSPs on their own if a database is simply read,
but any updates or new records to be added will more safely be done using
Enterprise JavaBeans.
Q What type of EJB should I typically use to encapsulate business logic? And
which type would I use to contain data and its associated operations?
A For pure business logic, you would typically use a session bean (or a messagedriven bean). If the EJB is to represent underlying application data, you would
probably use an entity bean.
Q Why are JSPs generally faster than other server-side scripting environments?
A When a JSP is accessed, it is compiled into Java bytecodes. Every subsequent
access will use the bytecodes rather than processing the page again. This means
that JSPs will run as fast as standard Java classes such as servlets.
Q What are the consequences of producing a J2EE application with vendor specific APIs?
A The application you produce will become specific to a particular container, server,
or vendor. You will not be able to easily move the application from platform to
platform.
Q How do you package an EJB? What should be in the package?
A An EJB is packaged in an EJB-JAR file. The EJB-JAR file contains the classes for
the EJB, any other resources, and a deployment descriptor that contains EJB metadata and describes the external resource requirements of the EJB.
Q What is an EAR file?
A This is an Enterprise Application Resource file that houses the application’s JAR,
WAR, and XML files. The Assembler takes on the responsibility of packaging the
EAR file, while the Deployer authenticates that the file conforms to the J2EE specification, adds the file to the J2EE server, and deploys the application.
Exercises—Case Study
To help understand the role of each of the technologies in the J2EE specification, a single
case study will be followed throughout the daily exercises. As you work through the 21
days, a functional implementation of a simple enterprise application will be developed.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 72
72
Day 2
The Job Agency
The chosen case study is a simple Job Agency. Jobs are categorized by Location and
Skills required for the job. Customers advertise jobs, and Applicants register their location and skills so they can be matched to jobs. Customers and applicants will be notified
of job matches by e-mail.
To illustrate the relationships between the different components in the data model for the
Agency, a traditional database ERD diagram is shown in Figure 2.16.
FIGURE 2.16
Case Study ERD.
Applicant
login:
name:
email:
location:
summary:
VARCHAR(16) - PK
VARCHAR(64)
VARCHAR(64)
FK-Location
VARCHAR(512)
ApplicantSkill
applicant:
skill:
FK-Applicant
FK-Skill
Location
\PK
/
name:
VARCHAR(16) - PK
description: VARCHAR(64)
Skill
name:
VARCHAR(16) - PK
description: VARCHAR(64)
Matched
JobSkill
job:
customer:
skill:
applicant:
customer:
job:
exact:
FK-Job
\
FK-Customer >PK
FK-Skill
/
Job
ref:
customer:
description:
location:
VARCHAR(16) \PK
FK-Customer /
VARCHAR(512)
FK-Location
Customer
login:
name:
email:
address1:
address2:
VARCHAR(16) - PK
VARCHAR(64)
VARCHAR(64)
VARCHAR(64)
VARCHAR(64)
FK-Applicant \
FK-Customer >PK
FK-Job
/
boolean
04 0672323842 CH02
3/20/02
9:37 AM
Page 73
The J2EE Platform and Roles
73
Today’s exercise, which is described later, will be to create the database and register it as
a javax.sql.DataSource to the J2EE RI.
The case study has a front office part with the following components:
• Maintaining the location and job lookup tables
• Adding customers and advertising jobs
• Registering job applications
2
The back office part consists of the following:
• Matching a new job against existing applicants
• Matching a new applicant against existing jobs
• Generating e-mails
Using the Agency Case Study
The example code shown in each day’s lesson will use the Customer functionality (jobs
and invoices) from the case study. At the end of each day’s work, you will be asked to
enhance the case study by adding the Applicant functionality (registering jobs) to the
system. A fully-worked solution for the exercises is provided on the CD-ROM included
with this book, so that you will have a working case study if you choose to omit any
day’s exercise.
Note
Material for many days exercises, particularly JNDI, JavaMail, JMS and Java
Connectors, will primarily use examples and exercises not related to the case
study. The last two days’ work discuss J2EE in the context of the wider context of Web applications and do not refer to the Agency case study.
Table 2.3 shows roughly what will be covered on each day. Don’t worry if some of the
terminology is new to you; all will become clear as you work your way through the
book.
TABLE 2.3
Daily Workout
Day
Lesson
Exercise
1
Introduce multi-tiered application architectures
No exercise
2
Introduce the J2EE platform, technologies and roles Install J2EE RI and case study
database.
3
Using JNDI naming and directory services
Write a JNDI namespace
browser.
04 0672323842 CH02
3/20/02
9:37 AM
Page 74
74
Day 2
TABLE 2.3
Continued
Day
Lesson
Exercise
4
Using data sources, environment entries,
and EJB references
Build and deploy a simple EJB
and client application with J2EE
RI.
5
Using Session EJBs to implement business logic
Add a Session bean to register
job applicants.
6
Using Entity EJBs to encapsulate access
to persistent data
Add Entity beans for the appli
cant data and refactor the register
Session bean.
7
Using Container Managed Persistence (CMP)
and Container Manage Relationships (CMR) with
entity EJBs
Refactor the applicant Entity
bean to use CMP.
8
Adding transaction management to Session and
Entity EJBs
Add transaction management to
the Applicant processing.
9
Using JMS topics and queues
Develop a simple chat room service.
10
Using Message-driven beans to implement back
office functionality
Use Message-driven beans to
match new or changed applicants
to advertised jobs.
11
Adding e-mail capabilities to back office
functionality
Use e-mail to send matched jobs
to applicants.
12
Developing Web-based applications using servlets
Develop a servlet front end to
create a new applicant for the
Agency case study.
13
Developing Web-based applications using Java
Server Pages
Use JSPs to register job
applicants.
14
Using custom Tag Libraries with JSPs
Refactor the register job JSP to
use Tag Libraries.
15
Adding security to restrict access to J2EE
application functionality and data
Add security to control access to
the job skills data.
16
Understanding XML and writing simple XML
documents
Refactor the messages sent to the
back office job/applicant matching functionality to use XML.
17
Using XSL to transform XML documents into
different data formats
Transforming an XLD document
into HTML for viewing in a Web
browser.
04 0672323842 CH02
3/20/02
9:37 AM
Page 75
The J2EE Platform and Roles
TABLE 2.3
75
Continued
Day
18
Lesson
Understanding design patterns and recognizing
patterns present (and absent) from the case study
Exercise
Identify which design patterns
can be applied to the case study
to improve maintainability.
19
Working with legacy systems using the
Connector architecture
Identify how the case study could
be linked into a legacy invoicing
system.
20
Exposing J2EE components as Web Services
Create a simple Java stock quote
class and expose it as a Web
Service. Create a client to
retrieve the stock quote information from the server.
21
Using XML-based registries and asynchronous
Web Services
Create a Web Service JobPortal
that will take a SOAP message
containing new agency customer
information and return a generated customer login ID.
By the end of the course, you will have a simple, but working, job agency enterprise
application. The agency will have both a GUI-based front end and a Web-based interface
and will have given you a good grounding in the relative strengths of each J2EE technologies and how to apply them.
Practice Makes Perfect
Developing J2EE architectures requires two disciplines:
• Good analysis and design skills
• Practical hands-on experience with the J2EE technologies
The first comes with time and experience, but the last few days lessons will help point
you in the right direction to becoming a J2EE designer.
The second discipline comes with practice. If you read this book and attempt all the
exercises, you will learn a lot more than if you just read the book and simply study the
example code shown.
The case study exercises are not complex. They have been designed to take between 30
minutes and 2 hours to complete. The exercises only use information presented and your
existing knowledge: you will need to know something about JDBC, Swing, and HTML,
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 76
76
Day 2
but you certainly don’t need to be an expert in these technologies. The book “Teach
Yourself Java in 21 Days” from SAMS Publishing is a good source for improving your
knowledge of JDBC and Swing should you require a little refresher course. More importantly, the exercises will give you hands-on coding experience using J2EE.
The Case Study Directory on the CD-ROM
On the CD-ROM included with this book, you will find all the Java software required to
develop all of the code shown in this book.
The CD-ROM contains a directory called CaseStudy. All the example code solutions to
the exercises are included in this directory.
There are 21 sub-directories corresponding to each days work. Each day will have one or
more of the following directories:
•
The complete Agency case study so far. This includes code from the
examples in the book and the completed exercise if this is based on the case study.
•
Examples The code for all the example programs show in the book where these
examples are not part of the case study.
•
Exercise Any existing code to be used as a starting point for the exercise.
Typically, this will be the Agency case study, including all the example code in the
book but excluding the code the reader needs to provide as part of the exercise.
•
A solution to the set problem if the exercise does not enhance the
Agency case study.
Agency
Solution
Installing the Case Study Database
The Job Agency case study requires a small database for storing information about customers, jobs, applicants, and invoices. A Java program to create the database has been
provided in the Day 2 exercises on the accompanying CD-ROM. The program uses the
Cloudscape database provided with the J2EE RI and can easily be adapted to work with
any JDBC compatible database.
Find the directory on the CD called CaseStudy\Day02\Exercise.
Inside this directory is a Java source file, class file, and two script files:
•
CreateAgency.java
A source file for a program to create the Agency database
under J2EE RI Cloudscape database.
•
CreateAgency.class
The compiled Java class file for CreateAgency.java
04 0672323842 CH02
3/20/02
9:37 AM
Page 77
The J2EE Platform and Roles
•
CreateAgency.bat
77
A Windows NT/2000 batch file to run the application to cre-
ate the database
•
CreateAgency.sh
Unix/Linux Bourne shell script to run the application to create
the database
To create the Agency database, you will need write permission to the J2EE installation
directory.
Follow the instructions shown earlier in today’s lesson for stopping the Cloudscape and
J2EE, servers and stop these servers if they are currently running. If you have installed
J2EE as suggested, you simply have to enter the following commands from a command
(or shell) window:
j2ee –stop
cloudscape -stop
The Java CreateAgency program provided in the Day 2 exercises will create the necessary database files in a sub-directory called Agency. To create and install the database,
you will need to do the following:
1. Copy all the files from the Day 2 exercises directory CD-ROM to the cloudscape
sub-directory of the J2EE SDK installation directory.
2. Change directories to the cloudscape sub-directory of the J2EE SDK home directory and then run the appropriate script program as follows:
Under Windows, type
cd %J2EE_HOME%\cloudscape
CreateAgency
Under Linux/Unix, type
cd $J2EE_HOME/cloudscape
./CreateAgency.sh
A new sub-directory called Agency will be created under the current cloudscape
directory.
3. Having created the database, you must now add a data source called Agency to
J2EE (data sources are discussed on Day 4, “Introduction to EJBs”). Run the following command to add the data source (the same command is used for both windows and Linux/Unix):
j2eeadmin -addJdbcDatasource jdbc/Agency
➥jdbc:cloudscape:rmi:Agency;create=true
If you have not included the J2EE bin directory in your program search path, you
will have to run the command as shown below:
2
04 0672323842 CH02
3/20/02
9:37 AM
78
Page 78
Day 2
Under Windows, enter the following command:
%J2EE_HOME%\bin\j2eeadmin -addJdbcDatasource
➥jdbc/Agency jdbc:cloudscape:rmi:Agency;create=true
Under Linux/Unix, enter the following command:
$J2EE_HOME/bin/j2eeadmin -addJdbcDatasource
➥jdbc/Agency jdbc:cloudscape:rmi:Agency;create=true
Finally, restart the Cloudscape and J2EE servers as described in the earlier section,
“Starting the J2EE Reference Implementation (RI).” Normally, you would start the J2EE
server and Cloudscape database server in separate windows by using the following commands:
j2ee –verbose
cloudscape -start
If you start the J2EE server as shown with the -verbose option, you will see the diagnostic output shown in Listing 2.4 (the line showing the Agency data source configuration is highlighted in bold).
LISTING 2.4
The J2EE Reference Implementation Startup Diagnostics
1: > j2ee -verbose
2: J2EE server listen port: 1050
3: Naming service started:1050
4: Binding DataSource, name = jdbc/DB2, url =
➥jdbc:cloudscape:rmi:CloudscapeDB;create=true
5: Binding DataSource, name = jdbc/DB1, url =
➥jdbc:cloudscape:rmi:CloudscapeDB;create=true
6: Binding DataSource, name = jdbc/Agency, url =
➥jdbc:cloudscape:rmi:Agency;create=true
7: Binding DataSource, name = jdbc/InventoryDB, url =
➥jdbc:cloudscape:rmi:CloudscapeDB;create=true
8: Binding DataSource, name = jdbc/Cloudscape, url =
➥jdbc:cloudscape:rmi:CloudscapeDB;create=true
9: Binding DataSource, name = jdbc/EstoreDB, url =
➥jdbc:cloudscape:rmi:CloudscapeDB;create=true
10: Binding DataSource, name = jdbc/XACloudscape, url = jdbc/XACloudscape__xa
11: Binding DataSource, name = jdbc/XACloudscape__xa,
➥dataSource = COM.cloudscape.core.RemoteXaDataSource@653220
12: Starting JMS service...
13: Initialization complete - waiting for client requests
14: Binding: < JMS Destination : jms/Queue , javax.jms.Queue >
15: Binding: < JMS Destination : jms/firstQueue , javax.jms.Queue >
16: Binding: < JMS Destination : jms/Topic , javax.jms.Topic >
17: Binding: < JMS Cnx Factory :
➥QueueConnectionFactory , Queue , No properties >
18: Binding: < JMS Cnx Factory :
➥jms/QueueConnectionFactory , Queue , No properties >
04 0672323842 CH02
3/20/02
9:37 AM
Page 79
The J2EE Platform and Roles
LISTING 2.4
79
Continued
19: Binding: < JMS Cnx Factory :
➥TopicConnectionFactory , Topic , No properties >
20: Binding: < JMS Cnx Factory :
➥jms/TopicConnectionFactory , Topic , No properties >
21: Starting web service at port: 8000
22: Starting secure web service at port: 7000
23: J2EE SDK/1.3
24: Starting web service at port: 9191
25: J2EE SDK/1.3
26: J2EE server startup complete.
You will test the Agency database configuration on Day 4 when you learn how to create
and deploy a simple EJB.
Congratulations, you have installed J2EE successfully and completed today’s exercise to
configure the Agency database for use with the other exercises in this book.
2
04 0672323842 CH02
3/20/02
9:37 AM
Page 80
05 0672323842 CH03
3/20/02
9:31 AM
Page 81
WEEK 1
DAY
3
Naming and Directory
Services
The previous days have discussed the background to enterprise computing concepts and introduced J2EE technologies such as EJBs and Servlets. This chapter will show how the Java Naming and Directory Interface (JNDI) supports the
use of many of the J2EE components.
In its simplest form, JNDI is used to find resources (such as EJBs) you have
registered via the J2EE server. Advanced use of JNDI supports sophisticated
storage and retrieval of Java objects and other information.
This day’s work will include
• Using Naming and Directory Services
• JNDI and X.500 names
• Obtaining a JNDI Initial Context
• Binding and looking up names
• Name attributes
• Objects and References
• JNDI events and security
05 0672323842 CH03
82
3/20/02
9:31 AM
Page 82
Day 3
Naming and Directory Services
A Naming Service provides a mechanism for giving names to objects so that you can
retrieve and use those objects without knowing the location of the object. Objects can be
located on any machine accessible from your network, not necessarily the local workstation.
A real-world example is a phone directory. It stores telephone numbers against names
and addresses. To find someone’s phone number is simply a matter of using his or her
name (and possibly address) to identify the entry in the phone book and obtain the stored
phone number. There are a few complications, such as finding the right phone book to
look in, but it is essentially fairly simple.
Incidentally, naming services have a similar problem to that of finding the right phone
book. This is known as obtaining a context. A name can only be found if you examine
the right context (phone book).
A Directory Service also associates names with objects but provides additional information by associating attributes with the objects.
The yellow pages phone directory is a simple form of a directory service. Here, businesses often include advertisements with additional information such as a list of products
sold, professional qualifications, affiliated organizations, and even location maps for their
premises. These attributes add value to the name entry. A directory service will normally
provide the ability to find entries that have particular attributes or values for attributes.
This is similar to searching the yellow pages phone book for all plumbers running a 24hour emergency service within a certain area.
Yellow page style phone books also store names under different categories—for example,
plumbers or lawyers. Categorizing entries can simplify searching for a particular type of
entry. These categorized entries are a form of sub-context within the directory context of
the local phone book.
Why Use a Naming Service?
Naming Services provide an indispensable mechanism for de-coupling the provider of a
service from the consumer of the service. Naming services allow a supplier of a service
to register their service against a name. Users, or clients, of the service need only know
the name of the service to use it.
Think of the phone book once more, and how difficult it would be to find someone’s
phone number without the phone book. Obtaining your friend’s phone number means
going to their home and asking, or waiting until you meet up with them again—which
may be difficult to organize because you can’t phone them to arrange the meeting.
05 0672323842 CH03
3/20/02
9:31 AM
Page 83
Naming and Directory Services
83
The phone book is a directory service. In fact, a phone book is often referred to as a
phone directory. The phone directory service lets you look up a person or company’s
phone book using their name as a key.
At the end of the day, it is very difficult to imagine a world without naming services.
What is JNDI?
JNDI is a Java API that defines an interface to Naming and Directory Services for Java
programs. JNDI is just an API and not, in itself, a Naming and Directory Service. To use
JNDI, an implementation of a Naming and Directory service must be available. JNDI
provides a service-independent interface to the underlying Service Provider implementation.
Figure 3.1 shows how the JNDI layer interfaces between the Java program and the underlying naming services. Additional naming services can be plugged into the JNDI layer by
implementing the Service Provider Interface (SPI) for JNDI.
FIGURE 3.1
Java Program
JNDI Architecture.
JNDI Application programming Interface (API)
Naming Manager
JNDI Service Provider Interface (SPI)
LDAP
DNS
NDS
CORBA
RMI
NIS
?
??
Common Naming Services
Figure 3.1 shows that JNDI supports several well-known naming services, including the
following:
• Domain Name System (DNS) is the Internet naming service for identifying
machines on a network.
• Novell Directory Services (NDS) from Novell provides information about network
services, such as files and printers. NDS is found primarily in environments where
the main networking software is Novell.
• Network Information Service (NIS) from Sun Microsystems provides system-wide
information about machines, files, users, printers, and networks. NIS is primarily
found on Solaris systems, but Linux and some other Unix platforms support it.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 84
84
Day 3
• Lightweight Directory Access Protocol (LDAP) is the approved standard for an
Internet naming service. LDAP is a true directory service and supports attributes as
well as names for objects. LDAP is fast becoming the de-facto directory service for
the enterprise.
JNDI also supports some more specialized naming systems. For example, CORBA for
distributed component programming and RMI for distributed Java programming.
Although there is no named service provider for Windows Active Directory, it is supported. Windows Active Directory supports an LDAP interface, and you can access it via the
JNDI LDAP Service Provider Interface.
Naming Conventions
Each naming service has its own mechanism for supplying a name. Perhaps the most
familiar naming convention is that of DNS, where every machine connected to the
Internet has a unique name and address. Most readers should recognize the following as
a host name used by DNS:
www.samspublishing.com
In contrast, LDAP names are based on the X.500 standard and use distinguished names
that look like the following fictitious example:
cn=Martin Bond, ou=Authors, o=SAMS, c=us
This format will also be familiar to users of Microsoft’s Active Directory service, whose
naming system is also based on X.500 but uses a forward slash to separate the various
name components:
cn=Martin Bond/ou=Authors/o=SAMS/c=us
These last two naming conventions have similarities in that they are both hierarchically
structured with the most specific name occurring first and the most general name (or
context) occurring last.
JNDI provides classes that support creating and manipulating structured names; but most
programmers will use simple strings that JNDI passes on to the underlying service with
minimal interpretation.
Some JNDI Service Providers may use names that are case sensitive, and some service
providers may not, it all depends on the underlying technology and environment. To
maintain portability of your applications, it is always best to avoid names that differ only
by letter case and also ensure that names are always spelled in a consistent manner.
05 0672323842 CH03
3/20/02
9:31 AM
Page 85
Naming and Directory Services
85
Using JNDI
JNDI is a standard component of JDK 1.3 and is, therefore, also part of J2EE 1.3. JNDI
is also included in J2EE 1.2 and is available as a standard Java extension for JDK 1.2
and earlier.
While developing code, the program’s CLASSPATH must include the location of the JNDI
class libraries. As long as the JAVA_HOME environment variable has been set up, the JNDI
classes will be available to the Java compiler.
Running a JNDI-aware program requires a JNDI service to be running and the classes for
that service to be available to the program. Typically, this requires the CLASSPATH to
include one or more JAR files provided by the JNDI provider or a J2EE server vendor.
For implementation-specific details, see the vendor’s documentation.
By default, running a J2EE server starts a naming service on the same machine. If the
default behavior isn’t required, you must change the J2EE server configuration to use an
existing JNDI server.
Using Sun Microsystems’ J2EE Reference
Implementation
Using JNDI with Sun Microsystems’ J2EE Reference Implementation (RI) is straightforward. Ensure that
• The J2EE_HOME variable exists
• The CLASSPATH variable includes the j2ee.jar file from the lib directory of the
J2EE home directory
Examples of how to do this both for Windows and for Unix are shown in this section.
J2EE RI for Windows
Under systems running Microsoft Windows NT or 2000, you can set the class path interactively with the following:
Set CLASSPATH=%J2EE_HOME%\lib\j2ee.jar;%CLASSPATH%
Typically, it is better to set the class path as a system-wide environment variable (via the
My Computer properties dialog). A suitable value is as follows:
.;%J2EE_HOME%\lib\j2ee.jar
The class path can also include additional JAR files and directories for other Java components.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 86
86
Day 3
It is important to define the current directory (.) in the class path; otherwise, the Java
compiler and runtime systems will not find the classes for the program being developed.
J2EE RI for Linux and Unix
Under Linux and Unix, set the class path with the following:
CLASSPATH=$J2EE_HOME/lib/j2ee.jar:$CLASSPATH
Starting the JNDI Server
Startup the J2EE RI server, as Day 2, “The J2EE Platform and Roles,” described, and the
JNDI server will start at the same time. You start the J2EE server by entering the following command from a command-line window:
j2ee –verbose
The J2EE server will run in that window until you close the window down or enter the
following shutdown command from another command-line window:
j2ee -stop
Obtaining an Initial Context
The first step in using the JNDI name service is to get a context in which to add or find
names. The context that represents the entire namespace is called the Initial Context and
is represented by a class called javax.naming.InitialContext and is a sub-class of the
javax.naming.Context class.
A Context object represents a context that you can use to look up objects or add new
objects to the namespace. You can also interrogate the context to get a list of objects
bound to that context.
The javax.naming package contains all the simple JNDI classes. Sub-packages within
the javax.naming package provide additional JNDI functionality, such as directorybased features like attributes.
The following code creates an initial context using the default JNDI service information:
Context ctx = new InitialContext();
If something goes wrong when creating the initial context, a NamingException is thrown.
Initial Context Naming Exceptions
The runtime system reports errors in using JNDI as a subclass of NamingException. The
exceptions most likely to occur for accessing the initial context are as follows:
05 0672323842 CH03
3/20/02
9:31 AM
Page 87
Naming and Directory Services
87
javax.naming.CommunicationException: Can’t find SerialContextProvider
This exception usually means the JNDI Server is not running, or possibly the JNDI properties for the server are incorrect (see the next section, “Defining the JNDI Service”).
javax.naming.NoInitialContextException:
➥Need to specify class name in environment or system property, or as an applet
➥parameter, or in an application resource file: java.naming.factory.initial
This exception occurs when the IntialContext class does not have default properties for
the JNDI Service Provider, and the JNDI server properties have not be configured explicitly (see the next section, “Defining the JNDI Service”).
javax.naming.NoInitialContextException: Cannot instantiate class: XXX
[Root exception is java.lang.ClassNotFoundException: XXX]
This exception occurs when the class path defined for the JNDI program does not
include the JNDI server classes (see the next section, “Defining the JNDI Service”).
javax.naming.ServiceUnavailableException:
➥Connection refused: no further information
[Root exception is java.net.ConnectException:
➥Connection refused: no further information]
This exception occurs when the JNDI properties for the program fail to match the JNDI
Service Provider currently in use (see the next section, “Defining the JNDI Service”).
Defining the JNDI Service
During program development, it is reasonable to use a JNDI service running on the local
machine that uses the default service provider supplied with the J2EE server. When you
deploy the program, you must make use of the enterprise-wide naming service for your
site. You will need to configure the program to use a specific naming server and not the
default one provided with your test J2EE server.
The parameters that you usually need to define for the JNDI service are as follows:
• JNDI service classname
• Server’s DNS host name
• Socket port number
A particular server vendor’s implementation may require additional parameters.
There are several ways of defining the JNDI service properties for a program, but you
only need to use one of them. You can either
• Add the properties to the JNDI properties file in the Java runtime home directory
• Provide an application resource file for the program
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 88
88
Day 3
• Specify command-line parameters to be passed to an application
• Specify parameters to be passed into an applet
• Hard-code the parameters into the program
The last option is the weakest approach, because it restricts the program to working with
one type of JNDI service provider on one specific host.
The first two options are the most suited to production environments. They both require
that you distribute simple text configuration files with the program.
JNDI Properties Files
An application resource file called jndi.properties defines the JNDI service. The
JNDI system automatically reads the application resource files from all components in
the program’s CLASSPATH and from lib/jndi.properties in the Java runtime home
directory (this is the jre sub-directory of the Java JDK home directory).
The following example from Sun Microsystems’ J2EE RI shows a typical jndi.properties file:
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
java.naming.provider.url=localhost:1099
java.naming.factory.url.pkgs=com.sun.enterprise.naming
Each entry in the property file defines a name value pair. The InitialContext object
uses these properties to determine the JNDI service provider.
The J2EE server vendor usually supplies a sample jndi.properties file defining the
properties that need to be configured with their server. You can find the J2EE RI
jndi.properties file in the lib/classes directory in the J2EE RI installation directory.
Normally, any given JNDI service will require the following named properties:
java.naming.provider.url
This defines the DNS host name of the machine running the JNDI service and the service
port number. This is the only property that the network administrator needs to customize.
The property value is a machine name with an optional colon and port number. By
default, JNDI uses port 1099, and most sites do not change this value. The default server
is usually localhost.
Consider a host called nameserver in the Sams Publishing domain
(samspublishing.com), the full URL including port number for a default JNDI server on
this host would be as follows:
nameserver.samspublishing.com:1099
java.naming.factory.initial
05 0672323842 CH03
3/20/02
9:31 AM
Page 89
Naming and Directory Services
89
You set this property to the classname (including the package) of the Initial Context
Factory for the JNDI Service Provider. This value effectively determines which JNDI
Service Provider you use. To use the default Naming Service supplied with the J2EE RI,
you would specify this property as follows:
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
java.naming.factory.url.pkgs
This property defines prefix package names the InitialContext class uses for finding
other classes JNDI requires. The J2EE RI uses the following value for this property:
java.naming.factory.url.pkgs=com.sun.enterprise.naming
More information on these and other JNDI properties can be found in the API documentation for the Context class and in the JNDI Tutorial from Sun Microsystems.
The simplest way to define the JNDI Service Provider is to configure every client’s Java
home directory to include the necessary JNDI properties. This approach suits an intranet
where all machines are centrally managed.
Another approach is to include a suitable JNDI properties file with the client program
and distribute everything as a JAR file (program class files and the jndi.properties
file). This suits Web-based intranets or extranets, where applets are used or where you
can distribute the client JAR file to users.
Application Properties
Using the -D option, you can supply the JNDI properties on the java command line of an
application. This has the disadvantage of requiring long command lines that are hard to
remember and easy to mistype. A way around this problem is for you to provide script
files to run the application on the target platforms; typically, you will supply batch files
for Windows and shell scripts for Linux and Unix.
The following is an example of a command line that defines the JNDI factory classes and
server:
java
➥-D
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
➥–D java.naming.provider.url=localhost:1099 MyClass
Providing a jndi.properties file in the application JAR file is a cleaner solution than
providing command-line parameters. However, using command-line parameters makes
the JNDI properties more apparent when customizing the application for a local site. It is
easy to overlook a jndi.properties file in a JAR file.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 90
90
Day 3
Applet Parameters
An applet can accept the JNDI properties as parameters, for example
<applet code=”MyApplet.class” width=”640” height=”480”>
<param name=”java.naming.factory.initial”
value= “com.sun.enterprise.naming.SerialInitContextFactory” >
<param name=”java.naming.provider.url”
“value= localhost:1099”>
</applet>
Using parameters with the applet HTML file makes the JNDI properties more apparent
when customizing the applet for a local site. A jndi.properties file in the jar file is
easily overlooked.
Hard-Coded Properties
The least desirable way to specify the JNDI properties is via hard-coded values in the
program. Hard coding the properties means including the JNDI classnames and the server name in the source code. This is undesirable because it means that should the network
architecture change, you must edit, recompile, and redistribute the program. Obviously,
you want to avoid this maintenance overhead if you can. The network architecture may
change if the JNDI service moves to a different server or you install a new JNDI Service
Provider.
The mechanism for defining the service in code is via a hash table of properties passed
into the InitialContext constructor:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
“com.sun.enterprise.naming.SerialInitContextFactory”);
env.put(Context. PROVIDER_URL,
“localhost:1099”);
Context ctx = new InitialContext(env);
Notice how the code uses symbolic constants from the Context class rather than using
strings representing the properties (such as “java.naming.factory.initial”). This
approach makes the code more portable should the property names change in future versions of Java or JNDI.
Binding JNDI Objects
After the initial JNDI context has been obtained, a program can look up existing objects
and bind new objects to the context.
When working with EJBs, the main JNDI activity is to look up existing bound objects;
the J2EE server does most of the binding of the objects automatically.
05 0672323842 CH03
3/20/02
9:31 AM
Page 91
Naming and Directory Services
91
Because this section discusses the binding of objects, you can skip it if your primary purpose for using JNDI is to obtain EJB and other references within a J2EE application.
Binding Objects
Binding an object means adding a name to the JNDI service and associating that name
with a Java object. The name and object are bound to a context. Listing 3.1 shows how a
text message can be bound to the name sams/book.
LISTING 3.1
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
Full Text of JNDIBind.java
import javax.naming.*;
public class JNDIBind
{
private final static String JNDI = “sams/book”;
public static void main(String[] args) {
try {
Context ic = new InitialContext();
ic.bind(JNDI,”Teach Yourself J2EE in 21 Days”);
System.out.println(“Bound “+JNDI);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
}
}
The object to be bound must implement the Serializable interface so that the name
server can store a copy of the object.
The Context.bind() method will fail with a NameAlreadyBoundException (which
extends NamingException) if an object is already bound to that name. Another subclass
of NamingException is thrown if there is some other form of error, such as an invalid
name. Remember that different Service Providers may have different naming conventions.
Binding Problems
A Service Provider may not support binding of all types of objects. If the service cannot
bind a particular object, it will throw an exception.
Using the default naming service for J2EE RI that uses a transient CORBA naming service, the class of the object must be in the CLASSPATH used by the J2EE RI JNDI server.
For now, this means using standard J2SE and J2EE classes or configuring the J2EE RI
services to include your class files. The recommended approach is to edit the user
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 92
92
Day 3
configuration file (userconfig.sh or userconfig.bat) in the bin directory of the J2EE
RI home directory, and add the required class directories or JAR files to the J2EE_CLASSPATH variable defined in the configuration file.
An alternative is to use a Web Service to dynamically upload the required class files.
Dynamic uploading of class files is dicussed in the “Loading Classes from a Code Base”
section, later in this chapter.
Some Naming Services (such as LDAP) may use security features to ensure that only
authorized programs can bind new objects. The bind() method can also fail if it violates
any security features of the underlying naming service. The “Security” section of today’s
lesson covers this in more detail.
Name Persistence
A bound object normally remains in the namespace until it is unbound. If the bound
name remains across server restarts, it is said to be persistent. Commercial servers, such
as NDS, Active Directory, and LDAP, are persistent name servers and store the bound
names and ancilliary information on disk (typically in a database).
The default naming service for Sun Microsystems’ J2EE RI is a transient service; it
reloads bound objects from configuration files in the SDK home directory whenever it is
restarted. This naming service will not retain objects bound with the Context.bind()
method across server restarts.
Rebinding Objects
You can use the rebind() method to solve the problem of bind() failing if a name is
already bound. For example,
ic.rebind(“sams/book”,”Teach Yourself J2EE in 21 Days”);
The code unbinds any existing object bound to that name and binds the new object in its
place.
Using rebind() is a good design technique when a programmer is sure the name will not
be in use by another component. The alternative is to explicitly unbind the old name first
if it is in use as discussed in the next section on “Unbinding Objects.”
Unbinding Objects
You can remove an object from a namespace by using the Context.unbind() method. A
program uses this method when it is closing down and needs to remove its advertised
service because a bound name is not automatically unbound when the program shuts
down.
05 0672323842 CH03
3/20/02
9:31 AM
Page 93
Naming and Directory Services
93
Another common use for unbind() is to test if a name is already in use and unbind the
old object before binding a new object. The advantage of using unbind() in preference
to rebind() is that you can verify that the object to be unbound is at least of the same
type as the new object to be bound.
String JNDI = “sams/book”;
try {
Object o = ic.lookup(JNDI);
if (o instanceof String)
ic.unbind (JNDI);
}
catch (NameNotFoundException ex) {}
ic.bind(JNDI,”Teach Yourself J2EE in 21 Days”);
This example rebinds a string bound to the name sams/book, but will fail with a
NameAlreadyBoundException if the name is bound to another class of object. This is a
better design approach than that of using the rebind() method.
Renaming Objects
You can rename objects using Context.rename() by specifying the old name and then
the new name as parameters. The new name must specify a name in the same context as
the old name. An object must be bound to the old name, and the new name must not
have a bound object; otherwise, a NamingException is thrown.
ic.rename(“sams/book”,”sams/teachyourself”);
JNDI Name Lookup
The most common use of JNDI is to look up objects that have been bound to a name. To
do this, you require two items of information:
• The JNDI name
• The class of the bound object
With this information in hand, object lookup is the simple matter of using the
Context.lookup() method to find the object and then to cast that object to the required
class.
Listing 3.2 shows a simple program to look up the name sams/book that was bound by
the program in Listing 3.1.
LISTING 3.2
Full Text of JNDILookup.java
1: import javax.naming.*;
2: public class JNDILookup
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 94
94
Day 3
LISTING 3.2
3: {
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20: }
Continued
private final static String JNDI = “sams/book”;
public static void main(String[] args) {
try {
Context ic = new InitialContext();
String name = (String)ic.lookup(JNDI);
System.out.println(JNDI+”=”+name);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
catch (ClassCastException ex) {
System.err.println(ex);
System.exit(1);
}
}
You can run the JNDIBind program in Listing 3.1 and then run this JNDILookup program
to print out the value of the string bound against sams/book.
Note
When casting an object that the lookup() method returns, that object’s
class must be in the client program’s class path. If this is not the case, the
program throws an exception.
Changing Contexts
The example name sams/book used in Listings 3.1 and 3.2 is an example of a Composite
Name. If you need to look up many names in the same context of a composite name
(names of the form sams/...), it is better to change to sub-context and look up the simple name within that context.
With this information in hand, the sub-context is a name entry just like any other name,
and you look it up in just the same way. The retrieved object is another Context object.
Listing 3.3 shows code that retrieves a name from a sub-context.
LISTING 3.3
Full Text of JNDILookupSAMS.java
1: import javax.naming.*;
2: public class JNDILookupSAMS
3: {
05 0672323842 CH03
3/20/02
9:31 AM
Page 95
Naming and Directory Services
LISTING 3.3
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20: }
95
Continued
public static void main(String[] args) {
try {
Context ic = new InitialContext();
Context ctx = (Context)ic.lookup(“sams”);
String name = (String)ctx.lookup(“book”);
System.out.println(name);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
catch (ClassCastException ex) {
System.err.println(ex);
System.exit(1);
}
}
Narrowing RMI-IIOP Objects
There is only one additional twist to the lookup tale, and that is when dealing with RMI
over IIOP objects.
The implementation of J2EE requires the use of RMI-IIOP to implement the remote
interfaces to EJB components. Consequently, when a lookup is for an EJB name (more
on this on Day 4, “Introduction to EJBs”), you cannot cast the returned object to the
required class; instead, you must narrow it.
RMI-IIOP uses a portable remote object to encapsulate information about the real remote
object. A portable remote object contains information about the real bound object in a
portable format that can be interogated by the recipient to find the real remote object.
The process of obtaining the real object from the portable remote object is called narrowing.
You use the PortableRemoteObject.narrow() method in the javax.rmi package to narrow a protable remote object to obtain the actual remote object. The narrow() method
takes two parameters:
• The object to narrow
• A java.lang.Class object defining the real remote object’s class
Listing 3.4 previews the discussion on Day 4 about EJB objects, but also serves to illustrate the use of the narrow() method.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 96
96
Day 3
LISTING 3.4
Narrowing an EJB Home Object
1: InitialContext ic = new InitialContext();
2: Object lookup = ic.lookup(“java:comp/env/ejb/Agency”);
3: AgencyHome home = (AgencyHome)
➥PortableRemoteObject.narrow(lookup, AgencyHome.class);
If your primary purpose for understanding JNDI is to enable the lookup and use of EJBs
and other J2EE technologies (such as JDBC data sources and Message queues), you can
skip the rest of this day’s material and return to it at a later date.
Contexts
Contexts provide a hierarchical structure to JNDI names, and composite names group
together related names. The initial context provides a top-level view of the namespace
and any sub-contexts reflect the hierarchical composite name structure.
Listing Contexts
The namespace represents contexts as names, and you can look these up just like any
other name. You can obtain a listing of the names in a context by using Context.list().
This method provides a list of name and class bindings as a
javax.naming.NamingEnumeration, where each element in the enumeration is a
javax.naming.NameClassPair object. Listing 3.5 shows a simple program to list the
names and classes for the example sams sub context.
LISTING 3.5
Full Text of JNDIListSAMS.java
1: import javax.naming.*;
2: public class JNDIListSAMS
3: {
4:
public static void main(String[] args)
5:
{
6:
try {
7:
Context ctx = new InitialContext();
8:
NamingEnumeration list = ctx.list(“sams”);
9:
while (list.hasMore()) {
10:
NameClassPair item = (NameClassPair)list.next();
11:
String cl = item.getClassName();
12:
String name = item.getName();
13:
System.out.println(cl+” - “+name);
14:
}
15:
}
16:
catch (NamingException ex) {
17:
System.out.println (ex);
18:
System.exit(1);
05 0672323842 CH03
3/20/02
9:31 AM
Page 97
Naming and Directory Services
LISTING 3.5
19:
20:
21: }
97
Continued
}
}
You must run the JNDIBind program from Listing 3.1 before running this program; otherwise, the “sams” sub context will not exist. Running the program in Listing 3.5 will
produce a single line of output:
java.lang.String – book
The parameter to the list() method defines the name of the context to list. If this is the
empty string, the method lists the current context.
If the initial context of the J2EE RI namespace is listed, you must have the J2EE RI
classes in your search path; otherwise, you will get an org.omg.CORBA.BAD_PARAM exception caused by another CORBA exception:
org.omg.CORBA.MARSHAL: Unable to read value from underlying bridge :
➥ Serializable readObject method failed internally
➥ minor code: 1398079699 completed: Maybe
Don’t believe the completed:
complete.
Maybe
tagged on to the end of the error message. It didn’t
The easiest solution to this problem is to run the setenv script in the bin directory of
J2EE RI. This script creates a variable CPATH that you can use as the CLASSPATH for running J2EE RI client programs.
Under Windows use
%J2EE_HOME%\bin\setenv
java –classpath .;%CPATH% JNDIList
Under Linux and Unix use
$J2EE_HOME/bin/setenv
java –classpath .:$CPATH JNDIList
The CD-ROM accompanying this book includes the JNDIList program, which is the
same as the program in Listing 3.5 but without the parameter to the list() method.
The list() method returns the name and the bound object’s classname, but not the
object itself. It is a lightweight interface designed for browsing the namespace.
A second method, called Context.listBindings(), retrieves the object itself. The
method returns a NamingEnumeration, where each element is of type
listBindings()
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 98
98
Day 3
javax.naming.Binding. Access
methods in the Binding class support retrieval of the
information of the bound object. Listing 3.6 shows a simple recursive tree-walking program that is a useful diagnostic tool for examining JNDI namespaces.
LISTING 3.6
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:
Full Text of JNDITree.java
import javax.naming.*;
public class JNDITree
{
public static void main(String[] args) {
Context ctx=null;
try {
ctx = new InitialContext();
listContext (ctx,””);
}
catch (NamingException ex) {
System.err.println (ex);
System.exit(1);
}
}
private static void listContext (Context ctx, String indent) {
try {
NamingEnumeration list = ctx.listBindings(“”);
while (list.hasMore()) {
Binding item = (Binding)list.next();
String className = item.getClassName();
String name = item.getName();
System.out.println(indent+className+” “+name);
Object o = item.getObject();
if (o instanceof javax.naming.Context)
listContext ((Context)o,indent+” “);
}
}
catch (NamingException ex) {
System.err.println (“List error: “+ex);
}
}
}
Creating and Destroying Contexts
Binding a composite name will automatically create any intermediate sub-contexts
required to bind the name. Binding the name com/sams/publishing/book/j2ee in
days creates the following sub-contexts if they don’t already exist:
com
com/sams
com/sams/publishing
com/sams/publishing/book
21
05 0672323842 CH03
3/20/02
9:31 AM
Page 99
Naming and Directory Services
99
You can explicitly create contexts with the Context.createSubcontext() method. The
single method parameter is the name of the context. If this is a composite name, all intermediate contexts must already exist. The createSubContext() method will throw a
NameAlreadyBoundException if the name already exists.
Note
Contrary to the API documentation, the J2EE RI naming service will automatically create any intermediate contexts.
Listing 3.7 shows a simple program for creating arbitrary contexts.
LISTING 3.7
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
Full Text of JNDICreate.java
import javax.naming.*;
public class JNDICreate
{
public static void main(String[] args) {
try {
if (args.length != 1) {
System.out.println (“Usage: JNDICreate context”);
System.exit(1);
}
Context ic = new InitialContext();
ic.createSubcontext(args[0]);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
}
}
The Context.destroySubcontext() method can destroy contexts. Again, the single
method parameter is the name of the context. The context does not have to be empty,
because the method will remove from the namespace any bound names and sub-contexts
with the destroyed context.
Listing 3.8 shows a simple program for deleting arbitrary contexts.
Use this program with caution, because destroying the wrong context will render your
J2EE server unusable. If you are using the J2EE RI, restarting the J2EE server can rectify a mistake; this might not be the case with other servers.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 100
100
Day 3
LISTING 3.8
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
Full Text of JNDIDestroy.java
import javax.naming.*;
public class JNDIDestroy
{
public static void main(String[] args) {
try {
if (args.length != 1) {
System.out.println (“Usage: JNDIDestroy context”);
System.exit(1);
}
Context ic = new InitialContext();
ic.destroySubcontext(args[0]);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
}
}
The destroyContext() method can throw a NameNotFoundException if the name
doesn’t exist and a NotContextException if the bound name is not a context.
More on JNDI Names
JNDI has to support different naming conventions for different Service Providers in the
most transparent manner possible. Generally, programmers will specify JNDI names as
strings, but a little understanding of how JNDI interprets bound names will help circumvent many simple problems that can occur when using names.
Special Characters
JNDI applies minimal interpretation to names specified as String objects. JNDI uses the
forward slash character (/) as a name separator to provide a simple name hierarchy
called a Composite Name. It is conventional for these composite names to be used to
group related names (such as plumbers in the phone book). As an example, JDBC data
sources take names of jdbc/XXX and EJBs the form ejb/XXX. While this is only a convention, it does help separate different sorts of named objects within the JNDI name
space.
Composite and Compound Names
Composite names can span different naming systems. An LDAP name can combine with
a file system name to get a composite name:
cn=Martin Bond, ou=Authors, o=SAMS, c=us/agency/agency.ldif
05 0672323842 CH03
3/20/02
9:31 AM
Page 101
Naming and Directory Services
101
Here a filename (agency/agency.ldif) is appended to an LDAP name. How JNDI interprets this is up to the individual Service Provider.
Incidentally, JNDI calls structured names like the DNS and LDAP compound names.
JNDI does not interpret compound names, but simply passes them through to the Service
Provider.
Besides forward slash (/), JNDI also treats backslash (\), single quote (‘), and double
quote (“) characters as special. If a compound name or a component of a name contains
any of these characters, they must be escaped using the backslash character (\).
If the underlying Service Provider uses the forward slash as a name separator (for example, the CORBA name service), there appears to be a conflict between JNDI and the
Service Provider. In practice, there is unlikely to be a problem because JNDI recognizes
two sorts of name separation—weak and strong. JNDI always passes the entire name to
the Service provider. A strong name separation implementation (such as LDAP or DNS)
simply processes the first part of the composite name and returns the remainder to the
JNDI Naming Manager to pass on to other name services. A weak name separation
implementation will simply process the entire composite name.The COSNaming server
used in the J2EE RI uses weak separation, as does the RMIRegistry naming service.
For those programmers who need to do more that use names to look up and bind objects,
JNDI provides several classes for manipulating and parsing composite and compound
names. The JNDI name support classes in the javax.naming package are Name,
CompositeName, and CompoundName.
URLs
In certain contexts, JNDI recognizes a URL (Uniform Resource Locator). The primary
use of URLs is to identify the JNDI server usually through the
java.naming.provider.url property, as shown in the following:
java.naming.provider.url=ldap://localhost:389
You can also specify a URL as a parameter to the lookup() and bind() methods in the
Context class. For example,
Context ic = new InitialContext();
Object obj = ic.lookup(“ldap://localhost:389/cn=Winston,dc=my-domain,dc=com”);
This overrides the default context and forces JNDI to perform the lookup against the
specified server. You need to take care with this approach, because the class path must
contain the necessary Service Provider classes, and these must be able to process the
request bind or lookup operation. In practice, this means that the URL must use the same
Service Provider classes as the initial context.
3
05 0672323842 CH03
3/20/02
9:31 AM
102
Page 102
Day 3
Attributes
Attributes are a feature of a Directory service and are not available with simple name
servers. Typically, you use attributes with an LDAP server. The J2EE RI JNDI server is a
CORBA name server, and it does not support attributes.
An attribute is additional information stored with a name. Storing full name, address,
phone number, and e-mail with a person’s name is a common use of a directory service.
NDS uses attributes to control access to shared network drives and to configure a user’s
login environment.
A directory service stores attributes as values against a keyword (LDAP calls them IDs).
Directory services usually support searching for names (objects) that have certain attributes defined (or not defined). Searching often supports looking for names with certain
attributes that have a specific value (often wildcard pattern matching is supported). A
simple search of a personnel database under an LDAP server might be to find all names
whose surname is Washington.
LDAP uses a schema system to control which attributes an object must define and those
that it may define. Any attributes that you add or delete must not break the schema’s
requirements. LDAP servers may be able to disable schema checking, but this is usually
a bad idea because the schema was created for a purpose.
If you want to see the capabilities of attributes, you must have access to a directory server. The rest of this section is based on using an LDAP Directory Server.
Overview of LDAP X.500 Names
LDAP names conform to the X.500 standard that requires a hierarchical namespace. A
Distinguished Name (DN) unambiguously identifies each entry in the directory. The DN
consists of the concatenation of the names from the root of the directory tree down to the
specific entry.
X.500 focuses on the interoperability of different directory services rather than specifying how a directory service should define the DN. Consequently, different implementations of X.500 can each use a different syntax for representing object names (as shown
earlier when we compared LDAP and Microsoft Active Directory names).
The official specification for the X.500 Directory Service is available from the
International Telecommunications Union (ITU) Web site at
http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-RECX.500-199708-S.
LDAP uses a comma-separated list of names with the names specified from the lowest
entry up the tree to the higher entries.
05 0672323842 CH03
3/20/02
9:31 AM
Page 103
Naming and Directory Services
103
Names consist of name and value pairs with the names typically being those in the following list. Each name has a short code and a full name; it is usual to only use the short
code because the Distinguished Names are fairly long.
•
c (countryName)—ISO
two letter code for county such as us, uk, and so forth.
•
o (organizationName)—Organization
•
ou (organizationUnitName)—Organizational
or company name, such as samspublishing
unit, typically a division or depart-
ment within an organization
•
l (localityName)—Typically
•
cn (commonName)—Common
defines a location within an organizational unit
name (sometimes called personal name), usually the
name of the user or client
•
dc (domainComponent)—A
component part of a domain name (such as DNS
names)
•
uid (userid)—Typically
represents a login name
An example LDAP DN looks like the following:
cn=Martin Bond, ou=Authors, o=SAMS, c=us
This will be a familiar structure if you work with digital certificates whose names conform to the X.509 standard.
Obtaining an LDAP Server
Using an LDAP Directory Service requires the JNDI properties to specify the JNDI
Service provider from Sun Microsystems and to have an LDAP server running.
The J2EE RI does not include an LDAP server, so you will have to obtain one from elsewhere. Only certain operating systems provide LDAP servers. Windows NT, 2000, and
XP users will have to purchase the enterprise (or server) editions of the operating system,
which are typically significantly more expense than the usual desktop or professional
editions. Sun Microsystems’ Solaris 8 Operating Environment includes an LDAP server.
Linux and Unix users can download and install the OpenLDAP implementation, which is
an open source server available free of charge for personal use. The Open LDAP server
can be downloaded from http://www.openldap.org/software/download/.
Users of Microsoft Windows will have to make other arrangements as OpenLDAP is not
available for the platform. If an Active Directory server is accessible on the network or
you use the Enterprise (or Server) edition of the Operating System, this is a simple solution. Otherwise, Windows users are well advised to find a spare PC and install Linux on
that and use OpenLDAP.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 104
104
Day 3
Note
Interestingly, with today’s low cost of hardware, it may be cheaper for the
home user to purchase a second system to run Linux rather then purchase
the additional license required to run the Enterprise (or Server) versions of
Windows NT/2000/XP.
Using OpenLDAP
Given that many users experimenting with LDAP will probably use OpenLDAP on a
Linux server, a brief digression on configuring Linux/LDAP for the programs in the rest
of this section is useful. Other users must adapt the configuration for their own LDAP
servers.
Note
You only need to install Open LDAP if you want to evaluate the directory
service features of JNDI and do not have access to a suitable directory service on your network. The following discussion of LDAP is not necessary for
your understanding and use of J2EE.
The rest of this sub-section assumes that you have some knowledge of Linux and
OpenLDAP (at the very least, that you have successfully installed OpenLDAP on a Unix
server). The following OpenLDAP dicussion assumes that you have installed OpenLDAP
in the default location of /usr/local (for example OpenLDAP v2.0.15 will be in the
directory /usr/local/openldap- 2.0.15).
If you build and install OpenLDAP according to the supplied instructions, the process
results in an empty LDAP directory namespace. The following steps will populate that
namespace with a few sample entries. Make sure the slapd (LDAP) server is not running
before making the following changes (if you have just installed OpenLDAP then slapd
will not be running).
If you install OpenLDAP 2.0 with the recommended Berkeley database, it creates a
default empty database with the DN suffix of dc=my-domain,dc=com.
To create a new DN suffix of o=Agency,c=US, you must add the following lines to the
end of the slapd configuration file (/usr/local/etc/openldap/slapd.conf):
database
suffix
directory
rootdn
rootpw
index
index
ldbm
“o=Agency,c=US”
/usr/local/var/openldap-ldbm
“cn=Manager,o=Agency,c=US”
secret
objectclass
eq
cn,sn
pres,eq,sub,subany
05 0672323842 CH03
3/20/02
9:31 AM
Page 105
Naming and Directory Services
105
Having defined the DN, you must add some sample data to the database. Listing 3.9
shows a configuration file for populating the database with sample data for use with the
example programs shown in this section.
LISTING 3.9
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:
Full Text of agency.ldif
dn: o=Agency,c=us
objectclass: top
objectclass: organization
o: Agency
description: Job Agency
dn: ou=Customers,o=Agency,c=us
objectclass: top
objectclass: organizationalUnit
ou: Customers
dn: cn=All Customers,ou=Customers,o=Agency,c=us
objectclass: top
objectclass: groupofnames
member: cn=Winston,ou=Customers,o=Agency,c=us
member: cn=George,ou=Customers,o=Agency,c=us
cn: Customers
dn: cn=Manager,o=Agency,c=us
objectclass: top
objectclass: person
cn: Manager
sn: Manager
dn: cn=George,ou=Customers,o=Agency,c=us
objectclass: top
objectclass: person
cn: George
cn: George Washington
description: President
sn: Washington
dn: cn=Abraham,ou=Customers,o=Agency,c=us
objectclass: top
objectclass: person
cn: Abraham
cn: Abraham Lincoln
description: President
sn: Lincoln
dn: cn=Winston,ou=Customers,o=Agency,c=us
objectclass: top
objectclass: person
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 106
106
Day 3
LISTING 3.9
44:
45:
46:
47:
Continued
cn: Winston
cn: Winston Churchill
description: Prime Minister
sn: Churchill
Copy this file to the Linux server and call it agency.ldif. Ensure that the slapd LDAP
server is not running and, using the following slapdadd commandto install the sample
names in the OpenLDAP configuration:
slapadd -b “o=Agency,c=US” –l agency.ldif
You can check the database configuration by using the slapcat command as follows:
slapcat | more
If you make a mistake or want to change the database configuration, you must delete the
existing entries (slapadd will not replace an entry that already exists in the database).
You can delete the existing OpenLDAP database by removing all of the files in the database directory specified in the slapd.conf configuration file. The following command
will delete the default ldbm database used by slapd:
rm /usr/local/var/openldap-ldbm/*
You can now use slapadd to create the new database as shown previously.
You can start the slapd (OpenLDAP) server using the following command:
/usr/local/openldap-2.0.15/services/slapd/slapd –d 1
This will run the server in debug mode with diagnostic messages being displayed to the
screen.
In the next section, you will test your LDAP server setup. You will find that the
OpenLDAP database now has three entries for the Customer Organizational Unit:
cn=Abraham,ou=Customers,o=Agency,c=us
cn=George,ou=Customers,o=Agency,c=us
cn=Winston,ou=Customers,o=Agency,c=us
You must leave the slapd server running while you evaluate the examples shown in the
rest of this section. When you want to stop the server, simply use Ctrl+C to interrupt the
server or use the kill command to send the server a terminate signal.
Configuring JNDI to use LDAP
After an LDAP server is available for use, you must configure JNDI to use that server.
This requires that you to obtain a JNDI Service Provider for LDAP (this isn’t part of the
LDAP server) and configure the JNDI properties accordingly.
05 0672323842 CH03
3/20/02
9:31 AM
Page 107
Naming and Directory Services
107
JDK1.3 and J2EE RI 1.3 include an LDAP Service Provider from Sun Microsystems and
all you have to do to use LDAP is to configure the JNDI properties to use the LDAP service. The simplest way to do this is to create an empty text file in the current directory
called jndi.properties and add the following lines to this file.
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url=ldap://localhost:389
If the LDAP server is not running on the current machine, replace the name localhost
with the name or IP address of the actual LDAP server. Port number 389 is the default
LDAP port number, and you can omit it if LDAP is running on the default port (or
replace it by the actual port number if a non-standard port is being used).
Testing the LDAP Server
Before looking at attributes, it is worth checking that the LDAP server is up and running
with the sample data. Verify that you can look up one of these sample names by using
the simple JNDILoopAny script shown in Listing 3.10. The code reads the JNDI name
from the command-line arguments.
LISTING 3.10
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
Full Text of JNDILookupAny.java
import javax.naming.*;
public class JNDILookupAny
{
public static void main(String[] args) {
if (args.length != 1) {
System.err.println (“Usage: JNDILookupAny JNDIname”);
System.exit(1);
}
try {
Context ic = new InitialContext();
Object o = ic.lookup(args[0]);
System.out.println(args[0]+”=”+o);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
catch (ClassCastException ex) {
System.err.println(ex);
System.exit(1);
}
}
}
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 108
108
Day 3
Run the following command to check access to the LDAP server.
java JNDILookupAny “cn=Manager, o=Agency,c=us “
This will display an entry similar to the following:
ou=Customers,o=Agency,c=US=com.sun.jndi.ldap.LdapCtx@42719c
If the test doesn’t work, you must check your local LDAP configuration. The most likely
problem will relate to security. You may need to provide security credentials to the
LDAP server. The “Security” section at the end of today’s lesson briefly covers this.
Obtaining a Directory Context
Attributes are only supported by Directory Services and cannot be accessed through the
ordinary Context object. Instead, you must use a javax.naming.directory.DirContext
class. The DirContext is a sub-class of Context, and you can use it in place of a
Context when dealing with a Directory Service where you require directory functionality
(such as attributes). For example,
DirContext ic = new InitialDirContext();
The javax.naming.directory package contains the other attribute classes discussed
next.
Reading Attributes
Attributes are read from the context just like a name is looked up from the context. The
DirContext.getAttributes() method returns a NamingEnumeration that contains a collection of Attribute objects. Each Attribute has an ID (or key) and a list of values (an
attribute can have more than one value for the same key). Listing 3.11 shows how all the
attributes for an object can be listed.
LISTING 3.11
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
Full Text of JNDIAttributes.java
import javax.naming.*;
import javax.naming.directory.*;
public class JNDIAttributes
{
public static void main(String[] args) {
if (args.length != 1) {
System.err.println (“Usage: JNDIAttributes JNDIname”);
System.exit(1);
}
try {
DirContext ctx = new InitialDirContext();
Attributes attrs = ctx.getAttributes(args[0]);
05 0672323842 CH03
3/20/02
9:31 AM
Page 109
Naming and Directory Services
LISTING 3.11
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28: }
109
Continued
NamingEnumeration ae = attrs.getAll();
while (ae.hasMore()) {
Attribute attr = (Attribute)ae.next();
System.out.println(“ attribute: “ + attr.getID());
NamingEnumeration e = attr.getAll();
while (e.hasMore())
System.out.println(“
value: “ + e.next());
}
System.out.println(“END of attributes for “+args[0]);
}
catch (NamingException ex) {
System.out.println (ex);
System.exit(1);
}
}
Running this program against the sample data produces the following result:
> java JNDIAttributes “cn=George,ou=Customers,o=Agency,c=us”
attribute: description
value: President
attribute: objectClass
value: person
attribute: sn
value: Washington
attribute: cn
value: George
value: George Washington
END of attributes for cn=George,ou=Customers,o=Agency,c=us
A second form of the getAttributes() method allows you to provide an array of
attribute names, and it only returns the values for those attributes. It is not an error to
query an attribute that isn’t defined; it simply doesn’t return a value for that attribute.
The following fragment shows how to find the cn and sn attributes for a name:
String[] IDs = {“sn”, “cn”};
Attributes attrs = ctx.getAttributes(“cn=George,ou=Customers,o=Agency,c=us”,IDs);
Searching for Objects
A powerful and useful feature of attributes is the ability for you to search for names that
have specific attributes or names that have attributes of a particular value.
You use the Context.search() method to search for names. There are several overloaded forms of this method, all of which require a DN to define the context in the name
3
05 0672323842 CH03
3/20/02
9:31 AM
110
Page 110
Day 3
tree where the search should begin. The simplest form of search() takes a second parameter that is an Attributes object that contains a list of attributes to find. Each attribute
can be just the name or the name and a value for that attribute.
Listing 3.12 shows a simple program to find all names that have a surname (sn) defined
and a description of President.
LISTING 3.12
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:
Full Text of JNDISearch.java
import javax.naming.*;
import javax.naming.directory.*;
public class JNDISearch
{
private final static String JNDI = “ou=Customers,o=Agency,c=us”;
public static void main(String[] args) {
try {
DirContext ctx = new InitialDirContext();
// create case insensitive search attributes
Attributes match = new BasicAttributes(true);
match.put(new BasicAttribute(“sn”));
match.put(new BasicAttribute(“description”,”president”));
NamingEnumeration enum = ctx.search(JNDI, match);
while (enum.hasMore()) {
SearchResult res = (SearchResult)enum.next();
System.out.println(res.getName()+”,”+JNDI);
}
}
catch (NamingException ex) {
System.out.println (ex);
System.exit(1);
}
}
}
The search() method returns a NamingEnumeration containing objects of class
SearchResult (a sub-class of NameClassPair discussed earlier). The SearchResult
encapsulates information about the names found. The example program simply prints out
the names (the names in the SearchResult object are relative to the context that was
searched).
Running the program in Listing 3.12 will return the following values from the sample data:
cn=George,ou=Customers,o=Agency,c=us
cn=Abraham,ou=Customers,o=Agency,c=us
The SearchResult class also has a getAttributes() method that returns the attributes
for the found name. The simple search shown in Listing 3.12 returns all of the name’s
attributes.
05 0672323842 CH03
3/20/02
9:31 AM
Page 111
Naming and Directory Services
111
A second form of the search() method takes a third parameter that is an array of String
objects specifying the attributes for the method to return. The following code fragment
shows how to search and return just the surname and common name attributes:
NamingEnumeration enum = ctx.search(JNDI, match, new String[]{“sn”,”cn”});
Another form of the search() method takes a String parameter specifying a search filter.
The filter uses a simple prefix notation for combining attributes and values. The JNDI
API documentation and the JNDI Tutorial from Sun Microsystems provides full details
of the search filter syntax. Listing 3.13 shows a search for names with a description or
President or Prime Minister.
LISTING 3.13
Full Text of JNDIFilter.java
1: import javax.naming.*;
2: import javax.naming.directory.*;
3: public class JNDIFilter
4: {
5:
private final static String JNDI = “ou=Customers,o=Agency,c=us”;
6:
7:
public static void main(String[] args) {
8:
try {
9:
DirContext ctx = new InitialDirContext();
10:
SearchControls sc = new SearchControls();
11:
String filter =
➥”(|(description=President)(description=Prime Minister))”;
12:
NamingEnumeration enum = ctx.search(JNDI, filter, sc);
13:
while (enum.hasMore()) {
14:
SearchResult res = (SearchResult)enum.next();
15:
System.out.println(res.getName()+”,”+JNDI);
16:
}
17:
}
18:
catch (NamingException ex) {
19:
System.out.println (ex);
20:
System.exit(1);
21:
}
22:
}
23: }
You can use the javax.naming.directory.SearchControls argument required by
search() to
• Specify which attributes the method returns (the default is all attributes)
• Define the scope of the search, such as the depth of tree to search down to
• Limit the results to a maximum number of names
• Limit the amount of time for the search
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 112
112
Day 3
Running the program in Listing 3.13 with the sample data produces the following output:
cn=George,ou=Customers,o=Agency,c=us
cn=abraham,ou=Customers,o=Agency,c=us
cn=Winston,ou=Customers,o=Agency,c=us
Manipulating Attributes
The DirContext. ModifyAttributes() method supports the addition, modification, and
deletion of attributes for a name. To manipulate an attribute, the program must have write
permission to entries in the LDAP name server. On a live system, the program must supply valid user credentials when obtaining the initial context (see the “Security” section
later in this lesson). If you attempt to modify a name’s attributes without the requisite
permissions, a javax.naming.NoPermissionException is thrown.
If you are using the OpenLDAP server purely for evaluating JNDI, you can easily change
the permissions so that all users have write permission. Find the slapd configuration file
(by default, this is /usr/local/etc/openladap/slapd.conf) and replace the following
line:
access to * by * read
with
access to * by * write
Stop and restart the slapd server for this change to take effect.
You can manipulate attributes in one of two ways. The first, and most functional, is to
create an array of javax.naming.directory.ModificationItem objects. Each entry in
the array specifies an attribute ID and an operation (one of
DirContext.REPLACE_ATTRIBUTE, DirContext.ADD_ATTRIBUTE, and
DirContext.REMOVE_ATTRIBUTE). To modify or add a new attribute, the
ModifyAttributes() method requires an additional parameter for the value of the
attribute.
Listing 3.14 shows how the entry for Abraham can update the description attribute and
add a new seeAlso attribute.
LISTING 3.14
Full Text of JNDIModify.java
1: import javax.naming.*;
2: import javax.naming.directory.*;
3: public class JNDIModify
4: {
5:
private final static String JNDI =
➥”cn=Abraham,ou=Customers,o=Agency,c=us”;
05 0672323842 CH03
3/20/02
9:31 AM
Page 113
Naming and Directory Services
LISTING 3.14
113
Continued
6:
7:
public static void main(String[] args) {
8:
try {
9:
DirContext ctx = new InitialDirContext();
10:
SearchControls sc = new SearchControls();
11:
ModificationItem[] mods = new ModificationItem[2];
12:
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
13:
new BasicAttribute(“description”, “Assasinated
President”));
14:
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
15:
new BasicAttribute(“seeAlso”,
➥”cn=George,ou=Customers,o=Agency,c=us”));
16:
ctx.modifyAttributes(JNDI, mods);
17:
}
18:
catch (NamingException ex) {
19:
System.out.println (ex);
20:
System.exit(1);
21:
}
22:
}
23: }
After running this program, you can view the changes by using the JNDIAttributes program shown in listing 3.11.
> java JNDIAttributes “cn=abraham,ou=Customers,o=Agency,c=us”
attribute: seeAlso
value: cn=George,ou=Customers,o=Agency,c=us
attribute: description
value: Assasinated President
attribute: objectClass
value: top
value: person
attribute: sn
value: Lincoln
attribute: cn
value: Abraham
value: Abraham Lincoln
END of attributes for cn=abraham,ou=Customers,o=Agency,c=us
The second method for manipulating attributes is to define the operation and an
Attributes list of Attribute objects to be manipulated (with values if appropriate). The
next code fragment shows how to delete the seeAlso entry just added (which probably
should have referred to John F. Kennedy and not George Washington).
Attributes attrs = new BasicAttributes(“seeAlso”,null);
ctx.modifyAttributes(JNDI, DirContext.REMOVE_ATTRIBUTE, attrs);
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 114
114
Day 3
The next example shows how to change the description back to President.
Attributes attrs = new BasicAttributes(“description”,”President”);
ctx.modifyAttributes(JNDI, DirContext.REPLACE_ATTRIBUTE, attrs);
More on Objects
The early part of today’s lesson covered binding objects into a JNDI namespace. To
recap, a bound object must implement the Serializable interface, and the object’s class
file must be available to the JNDI server.
The obvious means of making an object’s class file available to the JNDI server is to set
the server’s class path to include the necessary directory or JAR file. However, this isn’t
always convenient, and the JNDI specification recognizes this situation and supports
dynamic loading of classes when using a directory service.
Loading Classes from a Code Base
Provided the JNDI Service Provider is a Directory Service and that service supports
Internet RFC 2713, the JNDI server can obtain necessary class files dynamically from
any HTTP server.
RFC2713 defines an interoperable way of storing Java objects in an LDAP server. By
defining how Java objects are stored and retrieved, the JNDI Naming Manager in Sun
Microsystems’ LDAP Service Provider can retrieve Java objects from the directory server
and recreate them on the client’s system.
You must configure the LDAP server to support the Java Schema defined in RFC2713. If
you are using the OpenLDAP server, as previously configured in the “Attributes” section,
supporting the Java Schema is a relatively simple change to the configuration:
1. Stop the OpenLDAP slapd daemon.
2. Edit the slapd configuration file (/usr/local/etc/openldap/slad.conf). After
the existing line starting with include, add the following line:
include /usr/local/etc/openldap/schema/java.schema
3. Save the file and restart the OpenLDAP server.
Defining a Code Base
From a programming point of view, Java class files must be made available via a Web
server. A javaCodebase attribute supplies the details of the Web server location when
binding the object into the JNDI directory namespace.
The following example uses the Sun Microsystems’ LDAP Service Provider and an
LDAP server, as discussed in the “Attributes” section earlier in today’s lesson.
05 0672323842 CH03
3/20/02
9:31 AM
Page 115
Naming and Directory Services
115
This example also requires an HTTP server to be running. Starting up the J2EE RI server
will also start an HTTP server on port 8000. The J2EE RI stores its HTTP pages in the
public_html directory in the J2EE RI home directory.
Listing 3.15 shows a simple class representing a book.
LISTING 3.15
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Full Text of Book.java
import java.io.*;
public class Book implements Serializable
{
String title;
public Book(String title) {
this. title = title;
}
public String toString() {
return title;
}
}
A Web server must make the book.class file available for download for the Sun
Microsystems’ LDAP Service provider to bind and look up Book objects. It is conventional to store downloadable class files in a separate sub-directory under the HTTP
server’s home directory. You normally call this sub-directory classes.
Use the following commands to copy the book.class file to the J2EE RI Web server. In
both cases, you require appropriate permission to write to the J2EE RI home directory.
Under Windows
mkdir %J2EE_HOME%\public_html\classes
copy Book.class %J2EE_HOME%\public_html\classes
Under Linux and Unix
mkdir $J2EE_HOME/public_html/classes
cp Book.class $J2EE_HOME/public_html/classes
With the class files in place, a Book object can be bound to the LDAP namespace as
Listing 3.16 shows. The code uses the javaCodebase attribute to specify the URL of the
directory containing the Java class files and not the class file itself.
LISTING 3.16
1:
2:
3:
4:
Full Text of JNDICodebase.java
import javax.naming.*;
import javax.naming.directory.*;
public class JNDICodebase
{
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 116
116
Day 3
LISTING 3.16
Continued
5:
private final static String JNDI = “cn=book,o=Agency,c=us”;
6:
private final static String codeURL =
➥”http://localhost:8000/classes”;
7:
8:
public static void main(String[] args) {
9:
try {
10:
DirContext ic = new InitialDirContext();
11:
Book book = new Book(“Teach Yourself J2EE in 21 Days”);
12:
Attributes attrs = new BasicAttributes();
13:
attrs.put(“javaCodebase”, codeURL);
14:
attrs.put(“cn”, “book”);
15:
ic.rebind(JNDI, book, attrs);
16:
System.out.println(“Bound “+JNDI);
17:
}
18:
catch (NamingException ex) {
19:
System.err.println(ex);
20:
System.exit(1);
21:
}
22:
}
23: }
Note that the example uses the code base URL of localhost:8000, which is required
because the J2EE web server uses a non-standard HTTP port (the standard HTTP port
is 80).
Note
The http.port entry in the web.properties file in the J2EE RI config directory
defines the default port for the Web server.
With the name registered and the HTTP server running, the client can look up the bound
object without having the Book class file in the search path. The Sun Microsystems’
LDAP Service Provider automatically loads the class file. Listing 3.17 shows a simple
client program.
LISTING 3.17
Full Text of JNDILookupBook.java
1: import javax.naming.*;
2: public class JNDILookupBook
3: {
4:
private final static String JNDI = “cn=book, o=Agency,c=us “;
5:
6:
public static void main(String[] args) {
7:
try {
05 0672323842 CH03
3/20/02
9:31 AM
Page 117
Naming and Directory Services
LISTING 3.17
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21: }
Continued
Context ic = new InitialContext();
Book book = (Book)ic.lookup(JNDI);
System.out.println(JNDI+”=”+book);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
catch (ClassCastException ex) {
System.err.println(ex);
System.exit(1);
}
}
3
References
Sometimes, storing a serialized copy of an object in the Directory Service is inappropriate. Perhaps the object is too large or you must instantiate it dynamically because its construction depends on information that can vary from one client to another.
JNDI references provide a mechanism for storing an object by reference rather than by
value. This mechanism only works if the underlying JNDI Service Provider supports
Referenceable objects. The LDAP Service Provider from Sun Microsystems supports
Referenceable objects.
Without going into too much detail, a reference to an object requires that a Factory class
is available to build the object from the information the reference stores. From a design
perspective, this requires two related classes:
• The Object class that must implement the javax.naming.Referenceable interface
• A Factory class that can create the required objects
The Referenceable interface requires an object to implement the getReference()
method, which returns a Reference object. The Reference object defines the name of
the class referred to and a Factory class that can be used to build the referenced object.
Listing 3.18 shows a simple Book reference class.
LISTING 3.18
1:
2:
3:
4:
117
Full Text of BookRef.java
import java.io.*;
import javax.naming.*;
public class BookRef implements Referenceable
{
05 0672323842 CH03
3/20/02
9:31 AM
Page 118
118
Day 3
LISTING 3.18
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19: }
Continued
String title;
public BookRef(String title) {
this.title = title;
}
public String toString() {
return title;
}
public Reference getReference() throws NamingException {
return new Reference(
BookRef.class.getName(),
new StringRefAddr(“book”, title),
BookFactory.class.getName(),
null);
}
The second parameter to the Reference constructor uniquely defines the object. This
requires a key to define the address type of the object (in this case, a String set to the
value book) and a value for this specific object (in this case, the book’s title). When the
object is reconstructed, this address type and value will pass to the Factory object.
The Factory class must implement either javax.naming.spi.ObjectFactory or
javax.naming.spi.DirObjectFactory, depending whether you use a Name Service
or a Directory Service. Both classes require the Factory class to implement a
getObjectInstance() method for creating reference objects. Listing 3.19 shows the
BookFactory class used in the BookRef class shown in Listing 3.16.
LISTING 3.19
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
Full Text of BookFactory.java
import javax.naming.*;
import javax.naming.spi.*;
import java.util.Hashtable;
public class BookFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name,
Context ctx, Hashtable env) throws Exception {
if (obj instanceof Reference) {
Reference ref = (Reference)obj;
if (ref.getClassName().equals(BookRef.class.getName())) {
RefAddr addr = ref.get(“book”);
if (addr != null) {
return new BookRef((String)addr.getContent());
}
}
}
return null;
}
}
05 0672323842 CH03
3/20/02
9:31 AM
Page 119
Naming and Directory Services
119
The factory getObjectInstance() method checks that it is passed a Reference object
and then checks that the class of the reference is a BookRef. If both of these conditions
are true, the factory uses the address type book to look up the value of the object and
then uses this to create a new BookRef object.
As far as name binding and object lookup are concerned, the client is unaware of the use
of references. Listings 3.20 and 3.21 show how your code can use the BookRef class.
LISTING 3.20
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
import javax.naming.*;
import javax.naming.directory.*;
public class JNDIBindBookRef
{
private final static String JNDI = “ book”;
public static void main(String[] args) {
try {
DirContext ic = new InitialDirContext();
BookRef book = new BookRef(“Teach Yourself J2EE in 21 Days”);
Attributes attrs = new BasicAttributes();
attrs.put(“cn”, “book”);
ic.rebind(JNDI,book,attrs);
System.out.println(“Bound BookRef “+JNDI);
}
catch (NamingException ex) {
System.err.println(ex);
System.exit(1);
}
}
}
LISTING 3.21
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
Full Text of JNDIBindBookRef.java
Full Text of JNDILookupBookRef.java
import javax.naming.*;
import javax.naming.directory.*;
public class JNDILookupBookRef
{
private final static String JNDI = “book”;
public static void main(String[] args) {
try {
DirContext ic = new InitialDirContext();
BookRef name = (BookRef)ic.lookup(JNDI);
System.out.println(JNDI+”=”+name);
}
catch (NamingException ex) {
System.err.println(ex);
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 120
120
Day 3
LISTING 3.21
15:
16:
17:
18:
19:
20:
21:
22: }
Continued
System.exit(1);
}
catch (ClassCastException ex) {
System.err.println(ex);
System.exit(1);
}
}
What Else Can JNDI Do?
JNDI is a large subject, and some of the previous discussion has been quite brief (there is
a lot more to attributes, searching, and references than has been shown). Today, the additional features, such as naming events and security, have been presented in a very superficial manner.
JNDI Events
JNDI supports an event model similar to the event listeners in the Java AWT and Swing
classes. However, the underlying JNDI Service Provider must also provide support for
the event model for a client to register event handlers.
The javax.naming.event package supports two types of JNDI event listener (both are
sub-classes of NamingListener):
•
NamespaceChangeListener
reports on changes to the namespace objects that are
added, removed, or renamed.
•
ObjectChangeListener
reports on changes to an object when its binding is
replaced or attributes are added, removed, or replaced.
Both interfaces define appropriate methods that are called when changes occur in the
JNDI namespace. A javax.naming.event.NamingEvent object is passed to the listener
method to define:
• The type of event (for example, name added or object changed)
• The name binding before the event occurred
• The name binding after the event occurred
You use the EventContext.addNamingListener() method to register a NamingListener
object against a context. Adding and removing a listener requires the context to implement the EventContext (or EventDirContext for Directory Services). The code to look
up and register a NamingListener is similar to that shown in Listing 3.22 (please use the
API documentation for further details):
05 0672323842 CH03
3/20/02
9:31 AM
Page 121
Naming and Directory Services
LISTING 3.22
1:
2:
3:
4:
5:
6:
121
Sample Code to Add a NamingListener
Context ic = new InitialContext();
EventContext ctx = (EventContext) ic.lookup(“sams”);
NamingListener listener = new MyNamingListener();
ctx.addNamingListener(“book”, EventContext.ONELEVEL_SCOPE, listener);
Listing 3.22 registers a listener against the sams context and listens for events on the
book name. Depending on the underlying Service Provider, the book name need not exist
when you register the listener.
You can register a NamingListener to listen for several objects either by listening on a
context (rather than an object) or by using attribute filters to specify the required objects.
JNDI event handling provides an effective means for monitoring changes to a namespace
to maintain up-to-date information about the registered objects.
Security
JNDI security depends on the underlying Service Provider. Simple services, such as RMI
and the CORBA name service (both of which the J2EE RI implementation supplies), do
not support security. These services allow any client to perform any operation.
In a production environment, security is paramount to ensuring the integrity of the data
in the JNDI server. Most live J2EE implementations will make use of LDAP to provide a
naming service that supports security.
LDAP security is based on three categories:
• Anonymous—No security information is provided.
• Simple—The client provides a clear text name and password.
• Simple Authentication and Security Layer (SASL)—The client and server negotiate
an authentication system based on a challenge and response protocol that conforms
to RFC2222.
If the client does not supply any security information (as in all the examples shown
today), the client is treated as an anonymous client.
The following JNDI properties provide security information:
•
•
java.naming.security.authentication is set to a String to define the authentication mechanism used (one of none, simple, or the name of an SASL authentication system supported by the LDAP server).
java.naming.security.principal
client to authenticate.
is set to the fully qualified domain name of the
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 122
122
Day 3
•
is a password or encrypted data (such as a
digital certificate) that the implementation uses to authenticate the client.
java.naming.security.credentials
If you do not define any of these properties, the implementation uses anonymous
(java.naming.security.authentication=none) authentication.
You can use a JNDI properties file to supply client authentication information, but more
usually you code the information within the client program. Usually, your application
must obtain the client authentication dynamically.
If you use strong (not simple or anonymous) authentication, the java.naming.security.authentication value can consist of a space-separated list of authentication mechanisms. Depending on the LDAP service provider, JNDI can support the following
authentication schemes:
• External—Allows JNDI to use any authentication system. The client must define a
callback mechanism for JNDI to hook into the client’s authentication mechanism.
• GSSAPI (Kerberos v5)—A well-known, token-based security mechanism.
• Digest MD5—Uses the Java Cryptography Extension (JCE) to support client
authentication using the MD5 encryption algorithm, which has no known decryption technique.
Day 15, “Security,” discuses the whole topic of J2EE and JNDI security.
Summary
JNDI provides a uniform API to an underlying naming or driectory service. A Naming
Service provides a means of storing simple information against a name so the information can be retrieved using the name as a key. A Directory Service stores additional
attribute information, as well as values against a name. Directory Services use attributes
to categorize names so that powerful searching of the directory tree structure can be supported.
JNDI supports any naming service provided a Service Provider implementation is available for the service. Standard services supported by JNDI include the following:
• Lightweight Directory Access Protocol (LDAP)
• Novel Directory Services (NDS)
• CORBA
• Active Directory is supported via its LDAP interface
05 0672323842 CH03
3/20/02
9:31 AM
Page 123
Naming and Directory Services
123
Using JNDI from within a Java program is a simple matter of creating a context and
looking up names within that context. The Context class supports naming services, and
the DirContext class supports directory services.
After a context has been defined, the lookup() method is used to retrieve the object
stored against a name. The bind() and rebind() methods are used to add or changes
bound objects, and the unbind() method is used to remove a bound object.
Within J2EE, JNDI is used to advertise components such as the following:
• EJBs
• Data sources (databases)
• JMS message queues and topics
Q&A
Q Why is a Name Service so important?
A Without JNDI, it would be a lot harder to provide services such as those implemented using J2EE objects like data sources, message queues, and EJBs. Each vendor would choose its own mechanism for defining how a client program should
gain access to the J2EE objects. Some might do this by distributing configuration
files, others by using TCP/IP broadcast network packets. Using a Name Service
provides a consistent means of providing network services in a portable and
platform-independent manner. Not only that, you can move an implementation of a
service from one machine to another. In this instance, the server simply updates the
Name Service entry to reflect its new location, and the whole process is transparent
to the client.
Q Why is JNDI so large? Surely all I need to do is map a name onto a Java
object?
A If all you want to do is support J2EE objects, JNDI could be as simple as a name
to object mapping service. But Sun Microsystems designed JNDI to interoperate
with established Directory Services, such as NDS, LDAP (Active Directory), and
DNS. By providing Java programming support for these services, the designers of
JNDI have ensured it will not be used as a proprietary product with J2EE servers,
but as a general interface to fully -functional directory services. This design philosophy also provides programmers with a mechanism for developing interfaces to
NDS and LDAP in Java rather than some other language, such as C++ or C#.
3
05 0672323842 CH03
3/20/02
9:31 AM
Page 124
124
Day 3
Exercise
You have been shown a simple program to display a JNDI namespace as a command line
program (JNDITree.java in Listing 3.6). Today’s exercise is to write a GUI version of
this program using the Swing JTree class.If you already know Swing, you can use
JNDITree.java as a guide for your program and go ahead and write your own JNDI
browser.
If you do not know Swing, the exercise directory for Day 3 on the accompanying CDROM includes a template program called JNDIBrowser.java for you to enhance. The
JNDIBrowser program handles all of the Swing initialization, all you have to do is get a
list of the names in the JNDI namespace and create a new
javax.swing.tree.DefaultMutableTreeNode representing the name and add this to the
JTree. When you add a name that is also a context, you need to add all the names in this
sub-context.
Comments have been added to the JNDIBrowser.java file to show you where to add
your code.
Don’t worry if this sounds complex—it isn’t. You only have to write about 12 lines of
code (most of which you can adapt from JNDITree.java).
Before you rush off and write your first piece of Java code for this book, remember that
you need to set up your CLASSPATH to include the J2EE Reference Implementation classes
using the setenv script, as discussed in today’s lesson. You can run the supplied solution
using the following commands.
Under Windows, use
%J2EE_HOME%\bin\setenv
java –classpath .;%CPATH% JNDIBrowser
Under Linux and Unix, use
$J2EE_HOME/bin/setenv
java –classpath .:$CPATH JNDIBrowser
If you complete this exercise or simply run the provided solution, you will see that the
JNDI names are listed in the order they were added to the context. As a second exercise,
change your program to display the names in alphabetical order. The solution called
JNDIBrowserSort.java program shows how this can be achieved using the
java.util.TreeMap class.
You will find these programs useful for browsing the JNDI namespace when you are
developing J2EE applications.
06 0672323842 CH04
3/20/02
9:25 AM
Page 125
WEEK 1
DAY
4
Introduction to EJBs
J2EE provides different types of components for different purposes. Today, you
will start to look at one of the principal types of component in J2EE—
Enterprise JavaBeans (EJBs).
The study of EJBs is continued on Day 5, “Session EJBs,”, Day 6, “Entity
EJBs”, Day 7, “CMP and EJB QL”, Day 8, “Transactions and Persistence”, and
Day 10, “Message-Driven Beans”. As you can see, there is a lot to learn about
EJBs, so today serves as a first step on the road to all of this EJB knowledge.
Today, you will
• Examine the different types of EJB available
• Take a look at how EJBs are applied
• Explore the structure of one of the EJBs that forms part of the case study
to see how the different parts fit together
• Deploy and use some of the EJBs from the case study
• Write a simple client for an EJB
First, you need to understand why you would use EJBs.
06 0672323842 CH04
3/20/02
9:25 AM
Page 126
126
Day 4
What Is an EJB?
In a typical J2EE application, Enterprise JavaBeans (EJBs) contain the application’s business logic and live business data. Although it is possible to use standard Java objects to
contain your business logic and business data, using EJBs addresses many of the issues
you would find by using simple Java objects, such as scalability, lifecycle management,
and state management.
Beans, Clients, Containers, and Servers
An EJB is essentially a managed component that is created, controlled, and destroyed by
the J2EE container in which it lives. This control allows the container to control the
number of EJBs currently in existence and the resources they are using, such as memory
and database connections. Each container will maintain a pool of EJB instances that are
ready to be assigned to a client. When a client no longer needs an EJB, the EJB instance
will be returned to the pool and all of its resources will be released. At times of heavy
load, even EJB instances that are still in use by clients will be returned to the pool so
they can service other clients. When the original client makes another request of its EJB,
the container will reconstitute the original EJB instance to service the request. This pooling and recycling of EJB instances means that a few EJB instances, and the resources
they use, can be shared between many clients. This maximizes the scalability of the EJBbased application. The EJB lifecycle is discussed further on Days 5 and 6.
The client that uses the EJB instance does not need to know about all of this work by the
container. As far as the client is concerned, it is talking to a remote component that supports defined business methods. How those methods are implemented and any magic performed by the container, such as just-in-time instantiation of that specific component
instance, are entirely transparent to the client part of the application.
The EJB benefits from certain services provided by the container, such as automatic
security, automatic transactions, lifecycle management, and so on. To do this, the EJB
must conform to certain rules and implement an appropriate interface that allows the
container to manage the component. The EJB is packaged with configuration information
that indicates the component’s requirements, such as transaction and security requirements. The container will then use this information to perform authentication and control
transactions on behalf of the component—the component does not have to contain code
to perform such tasks.
The primary purpose of the container is to control and provide services for the EJBs it
contains. When it needs to use some underlying functionality, such as creating a transaction on behalf of a bean, it uses the facilities of the underlying EJB server. The EJB
server is the base set of services on top of which the container runs. Different types of
06 0672323842 CH04
3/20/02
9:25 AM
Page 127
Introduction to EJBs
127
EJB will run in different containers, but many different EJB containers can run on a single EJB server. EJB servers are generally delivered as part of a J2EE-compliant application server (examples include BEA WebLogic and IBM WebSphere). You will install and
run the application server, which will provide the underlying services required of an EJB
server and will host EJB containers.
The EJB Landscape
As you have seen, the J2EE Blueprints (http://java.sun.com/blueprints/enterdefine a target architecture for a typical J2EE-based application. In
this architecture, EJBs live in the middle tier and are used by other application components that live in the presentation tier. Although it is possible that both of these logical
tiers will reside on the same computer, it is most likely that they will reside on different
machines. This means that an EJB will usually have to be made available to remote
clients.
prise/index.html)
To offer services to remote clients, EJBs will export their services as RMI remote
interfaces. RMI allows you to define distributed interfaces in Java. There are certain
caveats on doing this, not only at the implementation level (such as declaring that
RemoteExceptions may be thrown when calling a method on an EJB) but also at the
design level. Designing remote interfaces is a skill in itself, which will be explored as
you progress through topics in this book, such as EJBs and J2EE Patterns.
Because they must use an RMI-based interface to access the functionality of the EJB, the
clients of an EJB must have some programming functionality. This means that they are
typically either “thick” clients that provide a GUI interface or Web-server components
that deliver HTML interfaces to “thin” clients. The different types of client are explored
in more detail shortly.
In the other direction, EJBs themselves will make use of data sources, such as databases
and mainframe systems, to perform the required business logic. Access to such data and
services can be through a JDBC database connection, a J2EE Connector, another EJB, or
a dedicated server or class of some form.
Discovering EJBs
While it is quite easy to draw pictures of a 3-tier system containing boxes labelled
“EJB,” it is important to identify what application functionality should go into an EJB.
At the start of application development, regardless of the precise development process
used (Rational Unified Process (RUP), eXtreme Programming (XP), and so on), there is
generally some analysis that delivers a Unified Modelling Language (UML) domain
model (this identifies the main elements of the business problem to be solved). This can
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 128
128
Day 4
then form the basis of a solution model where the business concepts are mapped into
appropriate design-level artefacts, such as components. This is where EJBs come into the
design.
The UML model will consist of a set of classes and packages that represent single or
grouped business concepts. A class or package can be implemented as an EJB. Generally,
only larger individual classes will become EJBs in themselves, because EJBs are intended to be fairly coarse-grained components that incorporate a reasonably large amount of
functionality and/or data.
There are generally two types of functionality discovered during analysis—data manipulation and business process flow. The application model will usually contain data-based
classes such as Customer or Product. These classes will be manipulated by other classes
or roles that represent business processes, such as Purchaser or CustomerManager. There
are different types of EJB that can be applied to these different requirements.
Types of EJB
There are three different types of EJB that are suited to different purposes:
• Session EJB—A Session EJB is useful for mapping business process flow (or
equivalent application concepts). There are two sub-types of Session EJB —
stateless and stateful— that are discussed in more detail on Day 5. Session EJBs
commonly represent “pure” functionality that is created as it is needed.
• Entity EJB—An Entity EJB maps a combination of data (or equivalent application
concept) and associated functionality. Entity EJBs are usually based on an underlying data store and will be created based on that data within it.
• Message-driven EJB—A Message-driven EJB is very similar in concept to a
Session EJB, but is only activated when an asynchronous message arrives.
As an application designer, you should choose the most appropriate type of EJB based
on the task to be accomplished.
Common Uses of EJBs
So, given all of this, where would you commonly encounter EJBs and in what roles?
Well, the following are some examples:
• In a Web-centric application, the EJBs will provide the business logic that sits
behind the Web-oriented components, such as servlets and JSPs. If a Web-oriented
application requires a high level of scalability or maintainability, use of EJBs can
help to deliver this.
06 0672323842 CH04
3/20/02
9:25 AM
Page 129
Introduction to EJBs
129
• Thick client applications, such as Swing applications, will use EJBs in a similar
way to Web-centric applications. To share business logic in a natural way between
different types of client applications, EJBs can be used to house that business logic.
• Business-to-business (B2B) e-commerce applications can also take advantage of
EJBs. Because B2B e-commerce frequently revolves around the integration of
business processes, EJBs provide an ideal place to house the business process
logic. They can also provide a link between the Web technologies frequently used
to deliver B2B and the business systems behind.
• Enterprise Application Integration (EAI) applications can incorporate EJBs to
house processing and mapping between different applications. Again, this is an
encapsulation of the business logic that is needed when transferring data between
applications (in this case, in-house applications).
These are all high-level views on how EJBs are applied. There are various other EJBspecific patterns and idioms that can be applied when implementing EJB-based solutions.
These are discussed more on Day 18, “Patterns.”
Given this context, common types of EJB client include the following:
• A servlet or JSP that provides an HTML-based interface for a browser client
• Another EJB that can delegate certain of its own tasks or can work in combination
with other EJBs to achieve its own goals
• A Java/Swing application that provides a front-end for the business processes
encapsulated in the EJB
• A CORBA application that takes advantage of the EJB’s business logic
• An applet that takes advantage of the business logic in a remote EJB so that this
business logic does not need to be downloaded to the client
These are common ways that EJBs are applied. What benefits does the use of EJBs give
to you as a developer?
Why Use EJBs?
Despite the recommendations of the J2EE Blueprints, the use of EJBs is not mandatory.
You can build very successful applications using servlets, JSPs or standalone Java applications.
As a general rule of thumb, if an application is small in scope and is not required to be
highly scalable, you can use J2EE components, such as servlets, together with direct
JDBC connectivity to build it. However, as the application complexity grows or the number of concurrent users increases, the use of EJBs makes it much easier to partition and
scale the application. In this case, using EJBs gives you some significant advantages.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 130
130
Day 4
Hiding Complexity
Early middleware environments, such as “raw” CORBA, require the application developer to write a lot of code that interacts with the CORBA environment and facilitates the
connectivity and registration process. Such code can be likened to the plumbing that
pipes water around a house. It needs to be there but, as the user of a sink or shower, you
do not want to be intimately involved with it. In J2EE application terms, business developers want to write business code, not “plumbing” code. The EJB model tries to reduce
such interaction to a minimum by using the following mechanisms:
• Each bean conforms to a defined lifecycle and set of rules. This provides a distinct
boundary between system code and application code.
• Declarative attributes allow a developer to specify, say, the transactional behavior
of the component without having to write code to control such functionality.
• The deployment information provided with the deployable J2EE application provides information about the relationships between multiple EJBs and also defines
the resources required by an EJB.
Separation of Business Logic from UI and Data Access
One of the key facets of applying EJBs is that they allow business functionality to be
developed and then deployed independently of the presentational layer. You might, for
example, create an application with a user interface built using Java’s Swing API. This
application might then provide access to some business functionality for the employees
working on the company’s internal network. If the underlying business functionality is
implemented using EJBs, a different user interface could take its place without having to
redevelop the entire application. A Web-based interface that used servlets would make
the application available from the Internet without having to change a single line of code
in the business functionality. Figure 4.1 is a UML component diagram that shows this.
(More information on UML can be found in Appendix A, “An Introduction to UML,” on
the accompanying CD-ROM.)
It can sometimes be difficult to distinguish between the functionality that an application
provides and the user interface that is used to invoke that functionality. This is not unexpected because many common applications—such as a word-processor—are single-tier;
the presentational logic and the business functionality are a single entity. On the other
hand, consider programming a video recorder. Most modern video recorders can be programmed either directly on the console or through a remote control unit. Either user
interface will accomplish the task of recording your favorite TV show, but there is only a
single “application.”
06 0672323842 CH04
3/20/02
9:25 AM
Page 131
Introduction to EJBs
FIGURE 4.1
An application implemented using EJBs can
have more than one
user interface.
131
«swing»
user interface
«session EJB»
business functionality
«database»
persistence layer
«servlet»
user interface
Consider another example. In most supermarkets, a cashier is responsible for scanning
the items in your shopping cart and then requesting a payment for the total. However,
some supermarkets also offer a trust system, whereby the customer scans the items with
a mobile scanner as they place the item into the shopping cart. To pay for the goods in
the shopping cart, the customer simply swipes his or her own card, and then leaves with
the goods. Again, there is a single application (to purchase shopping items) but two different interfaces—the cashier’s till and the customer’s mobile scanner.
To implement a distributed application using EJBs, make sure you have distinguished
between the user interface and the underlying business function. The EJB itself is concerned only with the latter of these.
Container Services
The container provides various services for the EJB to relieve the developer from having
to implement such services, namely
• Distribution via proxies—The container will generate a client-side stub and serverside skeleton for the EJB. The stub and skeleton will use RMI over IIOP to communicate.
• Lifecycle management—Bean initialization, state management, and destruction is
driven by the container, all the developer must do is implement the appropriate
methods.
• Naming and registration—The EJB container and server will provide the EJB with
access to naming services. These services are used by local and remote clients to
look up the EJB and by the EJB itself to look up resources it may need.
• Transaction management—Declarative transactions provide a means for the developer to easily delegate the creation and control of transactions to the container.
• Security and access control—Again, declarative security provides a means for the
developer to easily delegate the enforcement of security to the container.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 132
132
Day 4
• Persistence (if you want)—Using the Entity EJB’s container-managed persistence
mechanism, state can be saved and restored without having to write a single line of
code.
All of these container services are covered in more detail as the book progresses.
Now that you know why you would want to use an EJB and how to apply it, you can
examine the inner workings of an EJB to understand how all the parts fit together.
What’s in an EJB?
So far, you have been presented with a “black box” view of an EJB; it provides business
functionality via an RMI remote interface, and it cooperates with its container to perform
its duties. To understand, use, and ultimately write EJBs, you will need to know more in
concrete terms about the Java programming artefacts that make up an EJB. In other
words, what’s in one?
The Business Interface
The primary purpose of an EJB is to deliver business or application logic. To this end,
the bean developer will define or derive the business operations required of the bean and
will formalize them in an RMI remote interface. This is referred to as the bean’s business
or remote interface as opposed to the home interface you will look at in a moment.
Note
You may see references to local EJB interfaces and wonder how these relate
to the current discussion. Don’t worry about local interfaces for the
moment; they are covered on Day 6 when you examine entity EJBs.
The actual methods defined on the remote interface will depend on the purpose of the
bean, but there are certain general rules concerning the interface:
• As with any RMI-based interface, each method must be declared as throwing
java.rmi.RemoteException in addition to any business-oriented exceptions. This
allows the RMI subsystem to signal network-related errors to the client.
• RMI rules also apply to parameters and return values, so any types used must
either be primitive, Serializable, or Remote.
• The interface must declare that it extends the javax.ejb.EJBObject interface. This
provides a handful of basic methods that you will encounter as you progress.
06 0672323842 CH04
3/20/02
9:25 AM
Page 133
Introduction to EJBs
Caution
133
Failure to conform to the rules about extending javax.ejb.EJBObject
and throwing RemoteException will cause the interface to be rejected by
tools that manipulate EJBs. Additionally, if you use parameter or return
types that do not conform to the rules, your bean will compile and even
deploy, but will fail with runtime errors.
The issue regarding object parameters and return values is worth considering for a
moment. When you pass a parameter into a local method call, a reference to the original
object is provided to be used within the method. Any changes to the state of the object
are seen by all users of that object because they are sharing the same object. Also, there
is no need to create a copy of the object—only a reference is passed.
On the other hand, when using RMI remote methods, objects that are serializable (implement the Serializable interface) are passed by value, whereas objects that are remote
(that is, EJBs) are passed by reference. Pass by value means that a copy of the object is
sent. This has several implications. First, users of a serializable object passed across a
remote interface will no longer share the same object. Also, there may now be some performance costs associated with invoking a method through a bean’s remote interface. Not
only is there the cost of the network call, but also there is the cost of making a copy of
the object so that it can be sent across the network. Most of the time, it will be serializable objects that are passed.
You can see an example of an EJB remote interface in Listing 4.1—in this case, the one
for the Agency EJB used in the case study.
LISTING 4.1
Remote Interface for the Agency EJB
package agency;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
public interface Agency extends EJBObject
{
String getAgencyName() throws RemoteException;
Collection findAllApplicants()
throws RemoteException;
void createApplicant(String login, String name, String email)
throws RemoteException, DuplicateException, CreateException;
void deleteApplicant (String login)
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 134
134
Day 4
LISTING 4.1
Continued
throws RemoteException, NotFoundException;
Collection findAllCustomers() throws RemoteException;
void createCustomer(String login, String name, String email)
throws RemoteException, DuplicateException, CreateException;
void deleteCustomer (String login)
throws RemoteException, NotFoundException;
Collection getLocations()
throws RemoteException;
void addLocation(String name)
throws RemoteException, DuplicateException;
void removeLocation(String code)
throws RemoteException, NotFoundException;
Collection getSkills()
throws RemoteException;
void addSkill(String name)
throws RemoteException, DuplicateException;
void removeSkill(String name)
throws RemoteException, NotFoundException;
List select(String table)
throws RemoteException;
}
The interface lives in a package called agency, which will be common to all the classes
that comprise the EJB. The definition imports java.rmi.* and javax.ejb.* for
RemoteException and EJBObject, respectively. The rest of the interface is much as you
would expect from any remote Java interface—in this case, passing Strings and returning serializable Collections.
Notice that all the methods must be declared as throwing RemoteException. This means
that the client will have to handle potential exceptions that may arise from the underlying
distribution mechanism. However, your application will probably want to employ exceptions itself to indicate application-level errors. These exceptions should be declared as
part of the remote interface, as shown by the use of NotFoundException and
DuplicateException in the Agency interface.
The Business Logic
After an interface is defined, there is the none-too-trivial task of implementing the business logic behind it. The business logic for an EJB will live in a class referred to as the
bean. The bean consists of two parts:
06 0672323842 CH04
3/20/02
9:25 AM
Page 135
Introduction to EJBs
135
• The business logic itself, including implementations of the methods defined in the
remote interface
• A set of methods that allow the container to manage the bean’s lifecycle.
Note
Although the bean itself must contain these elements, note that it is possible, indeed common, for non-trivial beans to delegate some or all of their
business functionality to other, helper, classes.
Drilling down into these areas reveals more about the structure of an EJB.
Implementing the Business Interface
The first thing to note is that the bean itself does not implement the remote interface previously defined. This may seem slightly bizarre at first sight, because the equivalent RMI
server would have to implement the associated remote interface. However, there is a very
good reason for this.
As you will see later, it is possible to ask the container to apply services, such as access
control, on behalf of the EJB simply by setting attributes in the EJB configuration information. To do this, the container must have some way of intercepting the method call
from the client. When it receives such a method call, the container can then decide if any
extra services need to be applied before forwarding the method call on to the bean itself.
Sticking with the security example, the container would examine security information
configured for the EJB before deciding whether to forward the method call to the bean or
to reject it. The details about access control are covered on Day 15, “Security,” but you
can see that it is necessary to interpose between the client and the bean to “automagically” deliver such services.
The interception is performed by a server-side object called the EJBObject (not to be
confused with the interface of the same name). The EJBObject acts as a server-side
proxy for the bean itself, and it is the EJBObject that actually implements the EJB’s
remote interface. Figure 4.2 shows the relationship between the client, the bean, and the
EJBObject.
As shown in Figure 4.2, the client calls the business methods on the EJBObject implementation. The EJBObject applies the required extra services and then forwards the
method calls on to the bean itself. The EJBObject is separate from the RMI stub and
skeleton that provide the remote procedure call capability.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 136
136
Day 4
FIGURE 4.2
The EJBObject acts as
a server-side proxy for
the bean itself.
Location
transparency
Security and
transactions
Home
RMI
Stub
Home
RMI
Skeleton
EJB Home
Implementation
Bean
RMI
Stub1
Bean
RMI
Skeleton
EJB
Object1
Client
Bean
Classes marked with 1 implement
the EJB’s remote interface
So, your bean must implement the business methods defined in the remote interface. The
container uses the method signatures defined in the interface, together with the Java
reflection API, to find the appropriate methods on the bean, so you must ensure that you
use the correct method signatures. Despite this, the bean should not implement the
remote interface itself (the reasons for this are discussed later). However, if you are using
a developer tool that supports the creation of EJBs, it will generally generate empty
methods for you to populate. Listing 4.2 contains the outlines of the business methods in
the example AgencyBean.
LISTING 4.2
Business Method Implementation Signatures for the AgencyBean
package agency;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
// Remaining imports removed for clarity
public class AgencyBean implements SessionBean
{
public String getAgencyName() {
// Code removed for clarity
}
public Collection findAllApplicants() {
// Code removed for clarity
}
public void createApplicant(String login, String name, String email)
throws DuplicateException, CreateException {
// Code removed for clarity
}
06 0672323842 CH04
3/20/02
9:25 AM
Page 137
Introduction to EJBs
LISTING 4.2
137
Continued
public void deleteApplicant (String login)
throws NotFoundException {
// Code removed for clarity
}
public Collection findAllCustomers() {
// Code removed for clarity
}
public void createCustomer(String login, String name, String email)
throws DuplicateException, CreateException {
// Code removed for clarity
}
public void deleteCustomer (String login) throws NotFoundException {
// Code removed for clarity
}
public Collection getLocations() {
// Code removed for clarity
}
public void addLocation(String name) throws DuplicateException {
// Code removed for clarity
}
public void removeLocation(String code) throws NotFoundException {
// Code removed for clarity
}
public Collection getSkills() {
// Code removed for clarity
}
public void addSkill (String name) throws DuplicateException {
// Code removed for clarity
}
public void removeSkill (String name) throws NotFoundException {
// Code removed for clarity
}
public List select(String table) {
// Code removed for clarity
}
// Remaining methods removed for clarity
}
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 138
138
Day 4
The detail of the method implementations have been removed for clarity, because the
main area of interest here is how the method signatures match up with those on the
remote interface. The contents of the methods are largely the creation and dispatch of
JDBC statements and handling the results from the queries.
Note that the bean does not implement the Agency interface. You can also see that various of the methods, such as addSkill(), declare that they throw an application-specific
exception—in this case, DuplicateException.
Note
Note that your bean methods will only throw business exceptions or standard Java exceptions. They should not throw java.rmi.RemoteException,
because such exceptions should only be generated by the RMI subsystem.
Providing Lifecycle Hooks
Remember that the intention of the EJB environment is that you will spend most of your
time writing business logic rather than network and database “plumbing.” Beyond writing the business logic, the only additional thing the bean writer needs to do is to provide
lifecycle “hooks” that allow the container to manage the bean.
Each of the different types of EJB discussed earlier has a slightly different lifecycle, but
the common parts are as follows:
• Bean creation and initialization
• Bean destruction and removal
• The saving and restoring of the bean’s internal state (if applicable)
The details associated with each type of bean lifecycle will be discussed as they are covered. For now, all you need to know is that
• An EJB will implement one or more lifecycle interfaces depending on its type. The
interfaces (SessionBean, EntityBean, MessageDrivenBean, and
SessionSynchronization) are defined in the javax.ejb package.
• The lifecycle methods will generally begin with ejb so that they can be easily distinguished from the business methods around them, for example, ejbCreate().
Listing 4.3 contains the lifecycle methods in the example AgencyBean.
06 0672323842 CH04
3/20/02
9:25 AM
Page 139
Introduction to EJBs
LISTING 4.3
139
Lifecycle Methods on the AgencyBean
package agency;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
// Remaining imports removed for clarity
public class AgencyBean implements SessionBean
{
private DataSource dataSource;
private String name = “”;
private void error (String msg, Exception ex) {
String s = “AgencyBean: “ + msg + “\n” + ex;
System.out.println(s);
throw new EJBException(s);
}
public void ejbCreate () throws CreateException {
try {
InitialContext ic = new InitialContext();
dataSource = (DataSource)ic.lookup(“java:comp/env/jdbc/Agency”);
name = (String)ic.lookup(“java:comp/env/AgencyName”);
}
catch (NamingException ex) {
error(“Error connecting to java:comp/env/Agency:”, ex);
}
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
dataSource = null;
}
private SessionContext ctx;
public void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
// Remaining methods removed for clarity
}
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 140
140
Day 4
As you can see, the example AgencyBean implements the SessionBean interface. This
means that it must implement the ejbCreate(), ejbRemove(), ejbActivate(),
ejbPassivate(), and setSessionContext() methods. The ejbCreate() method takes
on the role of constructor in that most of the bean initialization will take place in there.
The context passed in setSessionContext() provides a way for the bean to communicate with the container.
This concludes the examination of the bean internals for the time being. You will discover more as you learn about the specific types of EJB later.
Factory Information
For an EJB to be used by a client, the client must create a new instance or discover an
existing one. Finding and gaining access to the services of a traditional remote server is
relatively simple. Such a server will tend to start when the machine boots, reside in a
well-know location, and carry on running until the machine shuts down. However, EJBs
are far more dynamic than that. It is the ability to dynamically create and reuse beans
that provides the scalability inherent in the EJB model.
To facilitate the creation and discovery of EJBs, each type of EJB provides a home interface. The bean developer will provide an EJB home interface that acts as a factory for
that particular EJB. A home interface will extend the javax.ejb.EJBHome interface and
will contain the necessary methods identified by the bean developer that allow a client to
create, find, or remove EJBs.
There are two ways for a client to get hold of the EJB itself, depending on the type of
EJB (Session, Entity, or Message-driven) and the way it is intended to be used. The EJB
Home interface can contain one or more create() methods to create a new instance of
an EJB. So, for example, you will create a new instance of a Session bean before using
it. On the other hand, when you interact with Entity EJBs, you will frequently find existing EJBs using one or more findXXX() methods. The home interface may or may not
allow you to remove the bean, depending on bean type and usage.
Listing 4.4 shows the home interface for the example Agency EJB.
LISTING 4.4
Home Interface for the Agency Bean
package agency;
import java.rmi.*;
import javax.ejb.*;
public interface AgencyHome extends EJBHome
{
06 0672323842 CH04
3/20/02
9:25 AM
Page 141
Introduction to EJBs
LISTING 4.4
141
Continued
Agency create () throws RemoteException, CreateException;
}
Because the Agency EJB is just a simple wrapper around some JDBC-based functionality
and does not maintain any business state, all that is required is a simple creation
method—create(). This maps onto the ejbCreate() seen in Listing 4.3. The client will
call create() to create an instance of the Agency bean.
The code underlying the home interface will work with the container to create, populate,
and destroy EJBs as requested by the client. The effects of the method calls will vary
depending on the type of EJB being manipulated. As a result, a request to remove a
Session EJB will just result in the EJB being thrown away, while the same request on an
Entity EJB may cause underlying data to be removed. The types and effects of different
home interface methods are discussed in more detail on subsequent days.
Bean Metadata
The final piece of the EJB jigsaw lies in the provision of configuration information, or
metadata, for the EJB. This provides a way of communicating the EJB’s requirements
and structure to the container. If an EJB is to be successfully deployed, the container will
have to be provided with extra information, including
• An identifier or name for the EJB that can be used to look it up.
• The bean type (Session, Entity, or Message-driven).
• Which class is the EJB’s remote interface. This interface will typically just be
named according to the EJB’s functionality, for example, Agency or BankTeller.
• Which class is the EJB’s home interface. The name for an EJB’s home interface
will typically be derived from its remote interface name. So, for example, the
Agency EJB has a home interface called AgencyHome. However, because this is a
convention rather than being mandatory, the metadata explicitly indicates the name
of the home interface.
• Which class is the bean itself. Again, the name for the bean will typically be
derived from the associated remote interface name. So, for example, the Agency
bean is called AgencyBean. However, because this is a convention rather than being
mandatory, the metadata explicitly indicates the name of the bean.
• Any name/value pairs to be provided as part of the bean’s environment.
• Information about any external resources required by the EJB, such as database
connections or other EJBs.
4
06 0672323842 CH04
142
3/20/02
9:25 AM
Page 142
Day 4
All of this essential information is bundled into a deployment descriptor that accompanies the EJB classes. As you might expect, given its recent rise as the most ubiquitous
way to define data, the deployment descriptor is defined as an XML document. The
deployment descriptor is discussed in more detail soon when examining the packaging of
an EJB.
In addition to the essential information, the deployment descriptor can also carry other
metadata that you will encounter as you progress:
• Declarative attributes for security and transactions
• Structural information bean relationships and dependencies
• Persistence mapping (if applicable)
You are now nearing the conclusion of this whistle-stop tour of the structure of an EJB.
After you have examined how an EJB is created and packaged, you will be ready to
deploy and use one.
How Do I Create an EJB?
You will create specific types of EJB as you progress through the book. However, the
creation of EJBs follows the same steps and principals for all types of EJB.
The Creation Mechanism
As you may have gathered from the previous discussion on EJB contents, the EJB developer must go through the following cycle:
1. Design and define the business interface. This may involve mapping from a UML
model of the solution into Java.
2. Decide on a bean type appropriate to the task in hand. Entity, Session, and
Message-driven beans all have their own pros and cons. If you choose to use a
Session bean, another question is whether to use a stateful Session bean or a stateless Session bean. Choice of the appropriate type is discussed in more detail on
Days 5, 6, 7, and 10.
3. Decide which home interface methods are appropriate for the bean type and define
the home interface for the EJB.
4. Create (or generate) a “boilerplate” bean with correct lifecycle methods.
5. Create your business logic by filling out the business methods.
6. Fill out lifecycle methods to control creation, destruction and to manage state (if
applicable).
06 0672323842 CH04
3/20/02
9:25 AM
Page 143
Introduction to EJBs
143
If your EJB classes are written correctly, all that remains is to wrap them up as a deployable unit. However, there are certain caveats you should bear in mind while creating your
bean.
Caveats on Code Creation
Due to the managed nature of the bean lifecycle, the EJB container imposes certain
restrictions on the bean including:
• EJBs cannot perform file I/O. If you need to log messages or access files, you must
find an alternative mechanism.
• EJBs are not allowed to start threads. All threading is controlled by the container.
• EJBs cannot call native methods.
• EJBs cannot use static member variables.
• There is no GUI available to an EJB, so it must not attempt to use AWT or JFC
components.
• An EJB cannot act as a network server, listening for inbound connections.
• An EJB should not attempt to create classloaders or change factories for artifacts,
such as sockets.
• An EJB should not return this from a method. Although not strictly a restriction
(the container will not prevent you from doing it), it is identified as being a very
bad practice. This relates to the earlier discussion that a bean should not implement
its associated remote interface. This would potentially give a client a direct remote
reference to the bean rather than the EJBObject. Instead, the bean should query its
EJB context for a reference to its associated EJBObject and return that to the caller.
For a full list of restrictions, see section 24.1.2 of the EJB 2.0 specification (available
online at http://java.sun.com/products/ejb/docs.html).
Create the Deployable Component
One alternative definition of a component is “a unit of deployment.” Following this
theme, a component should
• Contain all the information required to deploy it, above and beyond the classes.
This is the metadata discussed earlier.
• Be bound up in such a way that it can easily be transported and deployed without
losing any parts along the way.
Consequently, after the classes and interfaces for an EJB have been created, the following steps must be performed:
4
06 0672323842 CH04
3/20/02
144
9:25 AM
Page 144
Day 4
1. Capture the EJB’s metadata in a universally understood format. This takes the form
of an XML-based deployment descriptor (DD).
2. Bundle the classes and deployment descriptor up in a deployable format, namely a
JAR file.
The Deployment Descriptor
The EJB specification defines a standard format of an XML deployment descriptor document that can house EJB metadata. The exact format of a deployment descriptor is usually hidden behind tools that manipulate them on your behalf. However, it is worth examining some of the contents of a deployment descriptor to see how the EJB fits together
and how extra information and metadata is provided.
Listing 4.5 shows the deployment descriptor for the example Agency EJB.
LISTING 4.5
Agency Bean EJB Deployment Descriptor
1: <?xml version=”1.0” encoding=”UTF-8”?>
2:
3: <!DOCTYPE ejb-jar PUBLIC
➥ ‘-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN’
➥ ‘http://java.sun.com/dtd/ejb-jar_2_0.dtd’>
4:
5: <ejb-jar>
6:
<display-name>Simple</display-name>
7:
<enterprise-beans>
8:
<session>
9:
<display-name>Agency</display-name>
10:
<ejb-name>Agency</ejb-name>
11:
<home>agency.AgencyHome</home>
12:
<remote>agency.Agency</remote>
13:
<ejb-class>agency.AgencyBean</ejb-class>
14:
<session-type>Stateless</session-type>
15:
<transaction-type>Bean</transaction-type>
16:
<env-entry>
17:
<env-entry-name>AgencyName</env-entry-name>
18:
<env-entry-type>java.lang.String</env-entry-type>
19:
<env-entry-value>J2EE in 21 Days Job Agency</env-entry-value>
20:
</env-entry>
21:
<security-identity>
22:
<description></description>
23:
<use-caller-identity></use-caller-identity>
24:
</security-identity>
25:
<resource-ref>
26:
<res-ref-name>jdbc/Agency</res-ref-name>
27:
<res-type>javax.sql.DataSource</res-type>
28:
<res-auth>Container</res-auth>
29:
<res-sharing-scope>Shareable</res-sharing-scope>
06 0672323842 CH04
3/20/02
9:25 AM
Page 145
Introduction to EJBs
LISTING 4.5
145
Continued
30:
</resource-ref>
31:
</session>
32:
</enterprise-beans>
33: </ejb-jar>
The essential parts of the deployment descriptor in Listing 4.5 are
• The <session> tag delimits the definition of the Agency EJB and indicates that it is
a Session EJB (lines 8 and 31).
• The <ejb-name> tag defines the name of the EJB, in this case Agency (line 10).
• The home and remote interface types (as defined by their fully-qualified class filenames) are specified by the <home> and <remote> tags, respectively (lines 11–12).
The type of the bean itself is defined by the <ejb-class> tag (line 13).
In addition, two other parts are of particular note at this point in time:
• An environment entry is defined between lines 16 and 20 by using the <enventry> tag. This indicates that a String property called AgencyName should be
made available to the bean. The value of the property is J2EE in 21 Days Job
Agency. The environment defined in the deployment descriptor is made available
through JNDI under the name java:comp/env. In this case, the agency name can
be retrieved by looking up the name java:comp/env/AgencyName. This lookup can
be seen in the ejbCreate() method of Listing 4.3.
• An external resource is defined in lines 25–30 using the <resource-ref> tag. This
defines that a DataSource should be made available to this EJB under the name
jdbc/Agency. As with the environment entry for the agency name, this resource is
made available through JNDI under java:comp/env, so the EJB can retrieve the
DataSource by looking up the name java:comp/env/jdbc/Agency. Again, this
lookup can be seen in the ejbCreate() method of Listing 4.3.
Note
It is important to realize that the name used for a <resource-ref> is only a
logical name. In other words, it is just a text string used by a component to
reference an external resource. In theory, the resource name used by the EJB
to refer to the data source could be anything (foo, for example) as long as it
ties in with the information in the deployment descriptor. However, by convention, such names are kept in line with the name you would expect to use
under JNDI. As a result, in this example, the data source resource is referred
to by the bean as jdbc/Agency and will be registered under JNDI with the
same name.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 146
146
Day 4
All of the EJB classes and the deployment descriptor should then be bundled up in a JAR
file. The deployment descriptor should be named ejb-jar.xml. If there are multiple
EJBs packaged in the same JAR file, the deployment descriptor will have multiple EJB
definitions in it. This JAR file is then termed an EJB-JAR file to denote its payload. The
JAR file itself can be called anything (within reason) and has a .jar file extension.
The EJB-JAR file can also contain any extra resources required by the EJB, such as
application-specific configuration information that does not fit in a deployment descriptor environment entry.
Enterprise Applications
Although the EJB-JAR file is now complete, it must form part of an application to serve
a useful purpose. J2EE defines that enterprise applications can be built from components
(Web, EJB, and application components). The key is how to define the relationships
between the different parts of the application—there must be some way of plugging
things together.
The answer is that there must be a description of the application itself, which components it uses, how those components relate to each other, and which specific resources
they use. This is the information provided by the Application Assembler and Deployer
roles.
To provide this information to the target J2EE platform, another level of deployment
descriptor is used —the J2EE deployment descriptor. The J2EE deployment descriptor
provides the following:
• A list of the components in the application
• Security role information
• Web root information for Web components
This information is stored in an XML file called application.xml. All of the constituent
component JAR files (such as EJB-JARs) and the J2EE deployment descriptor are then
bundled up in another JAR file, this time called an Enterprise Archive (EAR) file, which
has a .ear extension. The contents of the J2EE deployment descriptor will be covered in
more detail as you examine the different parts of the example enterprise application.
Is the application now ready to deploy? Unfortunately, the answer is “Not quite yet.” The
J2EE deployment descriptor does not cover information about how to map the application onto a specific J2EE application server, specifically
06 0672323842 CH04
3/20/02
9:25 AM
Page 147
Introduction to EJBs
147
• The JNDI name under which the application server will make the EJB available. In
the case of the Agency bean, this would mean that an entry was required to map the
bean name of Agency to the JNDI name under which the EJB is registered, for
example, ejb/Agency.
• Information about how the security roles defined map to underlying security principals (this is covered on Day 15).
So, yet another XML-based deployment descriptor is required to contain this information, this time an application server-specific one. This file contains extra mapping information, as previously described, and also any other container-specific information
required for a smooth deployment in that environment. This extra deployment descriptor
is also stored in the EAR file, ready to be accessed when the application is deployed.
Note
The specific deployment descriptor for the J2EE Reference Implementation
(RI) server is called sun-j2ee-ri.xml.
How Do I Deploy an EJB?
After an EJB is packaged, it can be deployed in an appropriate J2EE server. There is no
limit to the number of times an EJB can be deployed as a part of different applications.
Remember that J2EE defines a separate role for the application deployer. It may be that
for particular installations, databases, or other resource names need to be changed to
match the local environment. When configuring the application, the deployer can alter
this EJB or enterprise application metadata.
Plugging into the Container
When an EJB is deployed into a particular EJB container, the EJB must be plugged into
that container. To do this, an EJBObject must be generated based on the EJB’s remote
interface. This EJBObject will be specific to that EJB container and will contain code
that allows it to interface with that container to access security and transaction information. The container will examine the metadata supplied with the EJB to determine what
type of security and transaction code is required in the EJBObject.
The container will also generate the home interface implementation so that calls to create, find, and destroy EJB instances are delegated to container-defined methods.
The container will examine the EJB and enterprise application metadata and hook up
resource references. It will also provide an environment for the application components.
4
06 0672323842 CH04
3/20/02
9:25 AM
148
Page 148
Day 4
Finally, the container will register the home interface of the EJB with JNDI. This allows
other application components to create and find EJBs of this type.
Performing the Deployment
As mentioned previously, when deploying an EJB or enterprise application, the application developer taking on the J2EE role of deployer can choose to alter certain of the
metadata relating to the configuration of the application. Although this can be done manually, it is usually done through a GUI tool to make things easier and to keep things consistent.
After the EJB has been deployed, any subsequent changes to its functionality will mean
that the EJB must be re-deployed. If the enterprise application or EJB is no longer needed, it should be undeployed from the container.
How Do I Use an EJB?
Given that EJBs are middle-tier business components, they are of little use without a
client to drive them. As mentioned earlier, those clients can be Web components, standalone Java clients, or other EJBs.
Regardless of the type of client, using an EJB requires the same set of steps—namely,
discovery, retrieval, use, and disposal. These steps are covered in the next three sections.
Discovery
To create or find an EJB, the client must call the appropriate method on the EJB’s home
interface. Consequently, the first step for the client is to get hold of a remote reference to
the home interface. On Day 3, you looked at naming services and how these can be used
to register information in a distributed environment. In a J2EE environment, such a naming service is accessible through JNDI and can be used to store references to EJB home
interfaces.
The EJB container will have registered the home interface using the JNDI name specified
during deployment (as part of the deployment descriptor). This is the name that the client
should use to look up the home interface. Recall from the EJB deployment descriptor
shown in Listing 4.5 that the EJB name specified was Agency. When deploying the EJB,
the deployer has a chance to set the JNDI name by which clients will find this EJB. In
this case, you would expect the deployer to simply set a JNDI name of ejb/Agency so
that the client could find the home interface by looking up java:comp/env/ejb/Agency.
The following code shows the initial lookup required:
06 0672323842 CH04
3/20/02
9:25 AM
Page 149
Introduction to EJBs
149
try
{
InitialContext ic = new InitialContext();
Object lookup = ic.lookup(“java:comp/env/ejb/Agency”);
AgencyHome home =
(AgencyHome)PortableRemoteObject.narrow(lookup, AgencyHome.class);
...
}
catch (NamingException ex) { /* Handle it */ }
catch (ClassCastException ex) { /* Handle it */ }
As you can see, because the reference returned from JNDI is just an object, you must
narrow it to the home interface type you expect—in this case, AgencyHome. If there are
any problems with the JNDI access or if the wrong object type is returned, a
NamingException or ClassCastException will be thrown.
There is no magic here. The object returned by the JNDI is simply an RMI remote object
stub. This stub represents the home interface remote object created by the container when
the EJB was deployed. This can be seen in Figure 4.2.
Now that you have a reference to the home interface, you can create the EJB you want to
use.
Retrieval and Use
You can now call the create() method you saw defined on the AgencyHome interface in
Listing 4.4 as follows:
try
{
...
Agency agency = home.create();
System.out.println(“Welcome to: “ + agency.getAgencyName());
...
}
catch (RemoteException ex) { /* Handle it */ }
catch (CreateException ex) { /* Handle it */ }
The create() method returns a remote reference to the newly-created EJB. If there are
any problems with the EJB creation or the remote connection, a CreateException or
RemoteException will be thrown. CreateException is defined in the javax.ejb package, and RemoteException is defined in the java.rmi package, so remember to import
these packages at the top of your client class.
Now that you have a reference to an EJB, you can call its methods. The previous code
sample shows the getAgencyName() method being called on the returned Agency reference. Again, whenever you call a remote method that is defined in an EJB remote interface, you must be prepared to handle RemoteExceptions.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 150
150
Day 4
Note
You will see later that some types of EJB are found rather than created. In
this case, all steps are the same except that the create() method is replaced
by the appropriate finder method and find-related exceptions must be handled. You still end up with a remote reference to an EJB. All of this is covered later when Entity EJBs are discussed on Day 6.
Disposing of the EJB
You have now created and used an EJB. What happens now? Well, if you no longer need
the EJB, you can get rid of it in exactly the same way that you would get rid of a local
Java object or a remote Java object defined using RMI—by setting its reference to null
as follows:
// No longer need the agency EJB instance
agency = null;
When the local RMI runtime detects that the remote object no longer has any local references, it will trigger remote garbage collection for that object, which means that its
remote reference will time out. This will result in the object being de-referenced at the
server-side. In the case of the simple Agency bean (a stateless Session bean), this will
cause the bean to be destroyed.
Although it is possible to use the remove() method to get rid of the EJB, you would not
normally use this for such a simple bean. Use of this method is discussed in more detail
on Days 5 and 6.
Running the Client
You are now in a position to write a simple application client for the Agency EJB. After
you have written it, you will want to compile and run it.
Before compiling your client, you should ensure that you have j2ee.jar on your classpath. This JAR file lives in the lib directory under J2EE_HOME. If you are using an enterprise IDE, you may find that all the relevant classes are already in your classpath.
To compile and run the client, you will need the following:
• The J2EE classes. These must be accessible through the classpath.
• Access to the EJB’s home and remote interface class files via the classpath.
• RMI stubs for the home and remote interfaces. These can either be installed on the
local classpath or downloaded dynamically from the EJB server.
06 0672323842 CH04
3/20/02
9:25 AM
Page 151
Introduction to EJBs
151
• If the client does not have the JNDI name of the EJB compiled in, you may want
to provide this on the command line or through a system property.
When you deploy the EJB, you should be able to ask the container for a client JAR file.
This client JAR file will contain all of the classes and interfaces needed to compile the
client (as defined in the previous bulleted list). You should add this client JAR file to
your classpath when compiling your client.
In theory, this should be it. However, you will find that any form of security definition on
the server will require you to authenticate yourself before you can run the application. In
this case, you must explicitly use the client container to provide the required security
mechanism.
Note
The client container is called runclient under the J2EE RI.
Deploying and Using an EJB in the J2EE
Reference Implementation
You should now be in a position to write and test an EJB client. However, before you can
do that, you must deploy an EJB that it can use. In this section, you will look at how to
deploy an EJB in the J2EE Reference Implementation (RI) and how to then use it from a
simple client.
The J2EE on which your EJB is deployed will provide a complete server-side environment. It houses any EJBs, runs a Web Server for JSP/servlets, runs a naming server for
storing component location information, and provides database access. All J2EEcompliant application servers will do this—even a non-commercial version, such as the
J2EE RI. The RI also provides you with a ready-to-use database so you do not have to
concern yourself with hooking up to an existing database or installing a separate one.
To deploy and test your EJBs (and servlets/JSPs later), you only need a single machine.
Both the J2EE client and the J2EE server (and its EJBs, servlets and JSPs) can run on the
same machine. No connection to the Internet is required. The J2EE RI is available on
multiple platforms (Win32, Solaris, and Linux) and should be consistent across these
platforms, so that J2EE applications created on one platform can be deployed on another.
If you encounter problems at any stage, try referring to the troubleshooting section just
before today’s Summary.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 152
152
Day 4
Note
Before running any of the tools described in this section, you will need to
set the J2EE_HOME environment variable to the location on your hard drive
where you deployed the J2EE reference implementation. You should also
add the bin directory below J2EE_HOME to your executable search path
(%PATH% under Windows or $path under Unix/Linux) so that you can run J2EE
tools and batch files from the command line.
Opening the Case Study EAR File
To deploy and manipulate EJBs under the RI, you will use a graphic tool called deployBefore you start using this, you will need to do two things:
tool.
1. Ensure that you have created and configured your database environment as
described on Day 2.
2. Start the J2EE RI runtime environment and the associated Cloudscape database. To
do this, run the cloudscape and j2ee scripts/batch files found in the bin directory
under J2EE_HOME as follows:
cloudscape -start
j2ee -verbose
The use of the -verbose flag for J2EE is not strictly necessary, but you may find it
useful to help you understand what the J2EE server does when it starts up.
Now you are ready to run the deploytool. Again, this is a script/batch file found in the
bin directory under J2EE_HOME. When you run it, the GUI screen will appear as shown in
Figure 4.3.
You should now be able to open the initial agency enterprise archive provided in the JAR
subdirectory of the Day 4 exercise code on the CD-ROM (agency.ear). Do this through
the menus by selecting File, Open and then browsing for the file in the subsequent Open
Object dialog box. Select the EAR file and click the Open Object button. The agency
application will now be displayed in the list of applications, as shown in Figure 4.4.
All of the code for the Agency EJB that is contained in the Agency application can be
found in the agency subdirectory of the src directory under the Day 4 Exercise part of
the CD-ROM.
Now that the enterprise application is loaded in deploytool, you can examine its settings.
06 0672323842 CH04
3/20/02
9:25 AM
Page 153
Introduction to EJBs
153
FIGURE 4.3
The initial screen
shown by deploytool.
FIGURE 4.4
The Agency application
has now been loaded
by deploytool.
4
06 0672323842 CH04
3/20/02
9:25 AM
154
Page 154
Day 4
Examining the Case Study Application
You can use deploytool to examine and alter deployment descriptor information for the
application and, if necessary, for individual components, such as EJBs.
If you select the JNDI Names tab for the agency application, you will see information
about the resources that the application exports and consumes. This is largely based on
information defined in the application deployment descriptor and the container-specific
deployment descriptor described earlier in the “Enterprise Applications” section.
In Figure 4.5, you can see in the Application box that there is a single EJB in this initial
form of the application. That EJB can be referenced through JNDI using the name
ejb/Agency.
FIGURE 4.5
displays
the JNDI information
from the Agency application deployment
descriptor.
deploytool
In the References box, you can see that two of the components in the application use
external resources. First, you can see that the component named Agency (the EJB) uses a
resource called jdbc/Agency that is registered under JNDI as jdbc/Agency.
The References box also indicates that the application client, SimpleClient, references
the Agency EJB by using the name ejb/Agency that appears under JNDI as ejb/Agency.
06 0672323842 CH04
3/20/02
9:25 AM
Page 155
Introduction to EJBs
155
You can also examine the settings of the EJB through deploytool. Click the icon next to
the Simple JAR file symbol to show the EJBs contained in the Simple EJB-JAR file.
There is a single EJB in the JAR file called Agency. If you select the Agency EJB, you
will see the properties defined in the deployment descriptor for that EJB. Select the
Resource Refs tab to see what external resources this EJB uses, as shown in Figure 4.6.
FIGURE 4.6
You can examine the
deployment descriptor
information for a single EJB, such as the
external resources it
expects.
4
Figure 4.6 shows that the Agency EJB expects one resource called jdbc/Agency that is of
type javax.sql.Datasource. This is the EJB deployment descriptor information you
saw in Listing 4.5.
Figure 4.7 shows the environment entries for the Agency EJB. If you want to alter the
AgencyName defined there, you can just double-click the Value field and type in an alternative name. If you make any changes to the configuration of the application or any of its
components, the suffix (changed) will be added to the application name in the title bar.
06 0672323842 CH04
3/20/02
9:25 AM
156
Page 156
Day 4
FIGURE 4.7
Environment entries
can be viewed or edited through
deploytool.
Deploying the Case Study Application
You can deploy the server-side components of the agency application (in this case, a single EJB) using deploytool. To deploy them, select the agency application item (indicated by a blue diamond under the Applications folder in the explorer area on the left
side), and select Tools, Deploy from the deploytool menu. This will display the initial
deployment screen shown in Figure 4.8.
As you can see from Figure 4.8, the default target host is localhost. This is fine, presuming that your copy of the J2EE RI is running on the local machine. If not, you should
Cancel and add the appropriate server name through the File, Add Server menu item
before proceeding.
The other point to note for this screen is that it allows you to obtain a client JAR file.
Recall that this client JAR file will contain all of the classes required by a client of the
application being deployed. A pre-prepared client JAR file is provided in the jar subdirectory of the Day 4 exercise code on the CD-ROM (agencyClient.jar), but you will
need to obtain a client JAR file for any new applications or components you deploy. If
you check the Return Client Jar box, you can browse and select an appropriate location
to store the returned JAR file.
06 0672323842 CH04
3/20/02
9:25 AM
Page 157
Introduction to EJBs
157
FIGURE 4.8
You can select a server
on which to deploy an
enterprise application.
4
Caution
If you are deploying the agency application on an other machine (not
localhost), you should not use the pre-provided agencyClient.jar file.
Instead, select the option to return the client JAR file and add this JAR to
the classpath when you run the client later.
Click Next to move on to the JNDI Names screen. This gives the deployer another chance
to provide server-specific deployment information. Note that this is the same information
you saw earlier when examining the application’s JNDI information. For now, just click
the Next button. At the last screen, click Finish to deploy the application to the selected
server.
The progress of the deployment is shown in a separate window, as seen in Figure 4.9.
The blue and green bars are progress bars that will increase as the deployment proceeds.
Click OK when the deployment is complete.
When you are done, the agency enterprise application should have been deployed, as
shown in Figure 4.10. To view the applications deployed on a server, expand the Servers
folder in the explorer area on the left side and then expand the particular server, such as
localhost.
06 0672323842 CH04
3/20/02
9:25 AM
158
Page 158
Day 4
FIGURE 4.9
will show
you the progress of the
deployment.
deploytool
FIGURE 4.10
You can list the applications deployed on a
server.
Testing the Case Study Application
After you have successfully deployed the server-side components of the application
under the J2EE RI, you can run the test client to check that everything is okay.
06 0672323842 CH04
3/20/02
9:25 AM
Page 159
Introduction to EJBs
159
The test client, client.SimpleClient, is provided pre-compiled in the classes subdirectory. To run this, use the script/batch file runSimple that can be found in the run subdirectory. When you run the client, you will need to provide a username and password.
Use the username guest with a password of guest123. Your interaction with the client
should look something like the following:
Initiating login ...
Username = null
Enter Username:guest
Enter Password:guest123
Binding name:`java:comp/env/ejb/Agency`
Welcome to: J2EE in 21 Days Job Agency
Customer list: J2EE in 21 Days Job Agency
abraham
alfred
george
winston
Unbinding name:`java:comp/env/ejb/Agency`
If you are not able to run this test, refer to the “Troubleshooting the Case Study
Application,” section next.
As you can see, this has used the Agency EJB to list all of the customers in the job
agency database. The code for the client is provided in the client sub-directory of the
src directory. If you examine it, you will see that it is identical to the code shown earlier,
but with some extra code to invoke the customer listing methods on the EJB and display
the results.
If you examine the runSimple script/batch file, you will see that it uses the runclient
utility provided by the J2EE RI as follows:
runclient -client agency.ear -name SimpleClient -textauth
Note
The precise command line used will differ between platforms to define the
location of the agency EAR file using the correct direction of slashes (backslash or forwardslash). However, this makes no difference in how the command works.
This runs the client application directly from the EAR file without having to unpack it.
The command line specifies that the client is called SimpleClient and that it lives in the
agency.ear archive. It also specifies that simple, text-based authentication should be
used between the client and the server.
4
06 0672323842 CH04
160
3/20/02
9:25 AM
Page 160
Day 4
The other thing to note in the script/batch file is that the environment variable APPCPATH
is set before runclient is called. The runclient utility uses this environment variable to
find the client JAR file for the application (in this case, it points to agencyClient.jar).
This information will be needed at runtime, not only because the EJB’s interface class
files are needed, but also because it also contains RMI stubs that are targeted at the correct server and deployment information that is used to map resource names to the target
server’s JNDI names.
Should you need to re-compile the client, or to compile your own client, you will need to
add this client JAR file to your classpath.
Initial Naming Context for the Client
When creating a JNDI InitialContext, the client runtime must get hold of information
regarding the content of this initial context. In the case of runclient, it will find mappings in the deployment descriptors contained in the client JAR file. These mappings will
be used to set up the initial context for the client. Consequently, when the client gets a
new initial context and looks up java:comp/env/ejb/Agency, for example, the local naming service runtime will be able to use the pre-provided mapping information to match
this text string to the appropriate JNDI name.
Other application servers may use different mechanisms to set up this initial context, such
as passing a Properties object to the naming runtime containing the appropriate information.
Troubleshooting the Case Study Application
If you have problems running the case study application, check out the following possible issues:
• Have you started the J2EE RI (j2ee -verbose)? Make sure by locating its console
window or looking for it in the list of processes or applications on your machine.
• Have you started the cloudscape database (cloudscape -start)? Try running the
initial database test at the end of Day 2 to ensure that the data is present and that
the database server is running.
• Have you deployed the EJBs? By opening the EAR file, you are simply loading the
enterprise application into the deploytool environment. You must explicitly deploy
the application to the server you are using through the Tools, Deploy menu.
• Have you set the classpath correctly for your client? The client will need access
to the J2EE libraries in order to run.
06 0672323842 CH04
3/20/02
9:25 AM
Page 161
Introduction to EJBs
161
• Try re-creating the client JAR file when you deploy the J2EE application to your
server. Make sure that this client JAR file is on the classpath when you compile
and run the client application.
• Check the J2EE RI console window to see if exceptions or errors are shown there.
• Check the J2EE log files under the logs directory in J2EE_HOME. There is a directory below logs that is named after the machine on which the server is running.
Below this, there are two nested j2ee directories. In the lower of these, you will
find various log files that you can examine for errors.
If you still have problems and you suspect that there is a problem with the configuration
of your J2EE RI, you can either re-install the RI or you could try deleting the serverspecific repository directory and then re-starting your server. You will lose all of your
deployed J2EE applications, but you may find this easier than re-installing. Under the
J2EE_HOME directory, you will find a directory called repository, and below this there
will be a directory named after the server on which you are running this instance of
the RI (for example, if your hostname is “fred”, there will be a fred directory below
repository). Stop the J2EE RI, remove the directory that is named after your server, and
then start the J2EE RI again.
4
Summary
Today, you have seen common ways that EJBs are used in applications and why you
would want to use them. You have seen that an EJB will have a home interface, a business or remote interface, and an implementation. You have seen how the EJB container
will provide much of the underlying code to support the EJB, and that it relies on
detailed deployment information that defines the EJB’s requirements.
You have also seen that a J2EE application consists of components and deployment
information and how the server-side part of such an application can be deployed. You
have seen a client that is able to use such server-side components and the code required
to write such a client.
Q&A
Q How many Java classes and interfaces must I write to create an EJB?
A The EJB writer must define a remote (or business) interface, a home interface, and
the bean implementation itself.
06 0672323842 CH04
3/20/02
9:25 AM
162
Page 162
Day 4
Q Why does an EJB run inside a container?
A The container provides many services to the EJB, including distribution, lifecycle,
naming/registration, transaction management, security/authentication, and persistence. If the container did not exist, you would have to write all the code to interact
with these services yourself.
Q What issues surround passing an object as part of a remote method call?
A To be passed as an argument or return type, an object must be either serializable or
remote. If it is neither of these, an error will occur at runtime. If an object is
defined as serializable, a new copy will be created and passed/returned. This can
add to the overhead of making the method call, but it is a very useful tool when
trying to cut down the amount of network traffic between clients and EJBs (as you
will see later on Day 18).
Q Most of the deployment descriptor information is straightforward, but what is
the difference between a <resource-ref> and an <env-entry>, and what sort
of information is contained in each type of entry?
A A <resource-ref> is part of a deployment descriptor that defines an external
resource used by a J2EE component. The <resource-ref> will define a name and
type for a resource together with other information for the container. The information in a <resource-ref> is really for the container rather than for the EJB itself.
To access a resource defined in a <resource-ref>, you would use JNDI to look up
its name java:comp/env/jdbc/Agency.
On the other hand, an <env-entry> contains information that is intended for the
EJB itself rather than the container. It will define a name, a class type and a value.
The contents of <env-entry> elements are usually strings. Again, you would use
JNDI to look up its name, java:comp/env/AgencyName.
Exercises
The intention of this day is for you to familiarise yourself with the EJB environment and
the use of EJBs. To ensure that you are comfortable with these areas, you should attempt
the following tasks.
1. If you have not already done so, follow the steps to deploy the example Agency
EJB from the Day 4 Exercise directory on the CD-ROM.
2. Examine the information displayed by deploytool and make sure that you can
identify where the resource reference for the Agency JDBC connection is set,
where the environment reference for the agency name is set, and where the JNDI
name of the Agency EJB itself is set.
06 0672323842 CH04
3/20/02
9:25 AM
Page 163
Introduction to EJBs
163
3. Use the runSimple script/batch file provided under the Day 4 Exercise directory
on the CD-ROM to run the test client. Make sure that this client runs without
errors and successfully lists all the customers in the agency database.
4. Without referring to the example client (but referring to the material you have covered today), create your own simple test client for the Agency EJB from scratch.
This should just consist of a command-line client that creates an instance of an
Agency EJB and asks it for its name.
5. Try changing the name under which the EJB is registered in JNDI using deploytool. Change the JNDI name used by your client to find the Agency EJB and make
sure that it still works.
4
06 0672323842 CH04
3/20/02
9:25 AM
Page 164
07 0672323842 CH05
3/20/02
9:36 AM
Page 165
WEEK 1
DAY
5
Session EJBs
On Day 4, “Introduction to EJBs,” you learned that business functionality can
be implemented using Session beans, and you deployed a simple Session bean
into the EJB container. Today, you will learn
• The uses of Session beans in more detail
• The different Session bean types and how to specify, implement, and
deploy both stateless and stateful Session beans
• About common practices and idioms when using Session beans
Overview
Session beans are a key technology within the J2EE platform because they
allow business functionality to be developed and then deployed independently
of the presentational layer.
For example, you might create an application with a user interface built using
Java’s Swing API. This application might then provide access to some business
functionality for the employees working on the company’s internal network.
07 0672323842 CH05
166
3/20/02
9:36 AM
Page 166
Day 5
If the underlying business functionality is implemented as Session beans, a different user
interface could take its place without having to redevelop the entire application. A Webbased interface would make the application available from the Internet at a single stroke.
There are two types of Session beans, and a couple of analogies help explain the differences between them. You almost certainly will have used the so-called wizards—helpers
to guide you through some task—in any modern word-processing program or IDE. A
wizard encapsulates a conversation between you the user and the application running on
the computer. The steps in that conversation are dictated by the Next and the Back buttons. The wizard remembers the answers from one page, and these sometimes dictate the
choices for the next. When you are done, you select the Finish button and the wizard
goes away and does its stuff.
The wizard is analogous to a stateful Session bean. The wizard remembers the answers
from each page, or put another way, it remembers the state of the conversation. It also
provides some service, as characterized by the Finish button. This is precisely what a
stateful session bean does.
Here is another analogy, this time with databases. You may well have had cause to write
stored procedures. These are named routines (methods and functions) that are written in
a database vendor’s version of the SQL language (for example, PL/SQL for Oracle and
Transact-SQL for Microsoft SQL Server) and stored in the database. They provide a way
to implement business rules on the database.
To invoke a stored procedure, a client-side application needs to know just the name of
the stored procedure and the parameters it requires. No knowledge of the underlying
database schema is needed, so to call a stored procedure called find_jobs_by_
advertiser written in Transact-SQL, the client would use the following:
exec find_jobs_by_advertiser “winston”
Behind the scenes, this would probably run a query against the Job table, but the important thing is that the client does not need to know this detail.
A stored procedure is analogous to a stateless Session bean. The stored procedure just
provides a service and can be invoked by any client. You may be wondering why have
Session beans at all if stored procedures—which are a tried-and-trusted technology—
already solve the problem. But Session beans do have a number of advantages.
Implementing stateful conversations is cumbersome using stored procedures, but trivial
with Session beans. Also, stored procedures are written in some database vendor’s proprietary dialect of SQL, so they are not portable across RDBMS. Session beans are, of
course, written in Java, so they will be portable across any compliant EJB container.
Session beans provide a service to a client application. In other words, Session beans are
an extension of a client’s business functionality into the middle tier.
07 0672323842 CH05
3/20/02
9:36 AM
Page 167
Session EJBs
Note
167
The Unified Modeling Language offers a number of useful diagrams to help
specify an application, one of which is the use case diagram (see Appendix
A, “An Introduction to UML,” found on the CD-ROM accompanying this
book, for further details).
Each use case represents an item of business functionality that is required to
fulfill an end-user’s goal. However, while use case diagrams indicate the system boundary of the application being developed, the use cases themselves
typically say nothing about the actual look-and-feel of the system. In other
words, the presentational layer or user interface is not directly specified.
It is quite possible to directly relate UML use cases to Session beans. A given
use case specifies an item of business functionality, while the Session bean
implements that functionality. Neither are concerned with the detail of how
that functionality is presented to the user.
This direct correspondence of the logical design (as characterized here in
UML use cases) to the physical implementation (in this case, Session beans) is
one of the reasons that the J2EE platform is so appealing, quickly allowing
designs to be realized into working code.
The javax.ejb Package for Session Beans
Now it is time to add a little more detail. EJBs are written by implementing various interfaces of the javax.ejb package. Some of these are implemented by the bean itself. In
other words, this is the code that you, the developer must write. Others are implemented
either directly by the EJB container or are implemented by classes generated by the tools
provided by your EJB container vendor, such as the J2EE RI.
Figure 5.1 shows a UML class diagram of the interfaces in javax.ejb that support
Session beans.
Central to the EJB architecture are the javax.ejb.EJBHome and javax.ejb.EJBObject
interfaces, common to both Session beans and Entity beans. These both extend the
java.rmi.Remote interface, meaning that the classes that implement them (not shown)
are available through RMI stubs across the network.
The javax.ejb.EJBLocalHome and javax.ejb.EJBLocalObject interfaces are local
equivalents, and the classes that implement these are accessible only locally (that is, by
clients that reside within the same EJB container itself). Because local interfaces are
most often used with Entity beans, and also because there’s plenty for you to learn about
today already, there’s no major discussion of them until tomorrow.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 168
168
Day 5
FIGURE 5.1
The javax.ejb package defines remote and
local interfaces, as
well as an interface for
the Session bean itself
to implement.
interface
EJBMetaData
interface
HomeHandle
getEJBHome
getHomeInterfaceClass
getRemoteInterfaceClass
getPrimaryKeyClass
isSession
isStatelessSession
getEJBHome
interface
EJBHome
interface
EJBLocalHome
remove
remove
remove
getEJBMetaData
getHomeHandle
interface
java.rmi.Remote
!
interface
EJBContext
getEJBHome
getEJBLocalHome
getEnvironment
getCallerIdentity
getCallerPrincipal
isCallerInRole
isCallerInRole
getUserTransaction
setRollbackOnly
getRollbackOnly
interface
Handle
interface
EnterpriseBean
getEJBObject
interface
SessionContext
interface
EJBObject
getEJBHome
getPrimaryKey
remove
getHandle
isIdentical
interface
EJBLocalObject
getEJBLocalHome
getPrimaryKey
remove
isIdentical
getEJBLocalObject
getEJBObject
interface
SessionBean
setSessionContext
ejbRemove
ejbActivate
ejbPassivate
The javax.ejb.EJBContext interface provides access to the home interfaces and, as you
can see from its method list, also provides security and transaction control. The
javax.ejb.SessionContext subclass is used only by Session beans and provides a reference to the bean’s EJBObject, that is, its interface for remote clients. Every EJB must
have a remote interface (or a local interface, discussed on Day 6, “Entity EJBs”).
The javax.ejb.HomeHandle and javax.ejb.Handle interfaces provide a mechanism to
serialize a reference to either a home or a remote interface for use later. This capability is
not often used, so isn’t discussed further.
The Session bean itself implements the javax.ejb.SessionBean interface that defines
the bean’s lifecycle methods and has an implementation for all of the methods defined in
the remote or the home interface.
Stateless Session Bean Lifecycle
You already know that there are two different types of bean—stateful and stateless.
You’ll be learning about both types today, first, the simpler stateless bean. The Agency
bean from the case study will be used for the example code.
07 0672323842 CH05
3/20/02
9:36 AM
Page 169
Session EJBs
169
Stateless beans hold no state for any particular client, but they do have a lifecycle—and
thus different states—imposed on them by the EJB architecture. Specifically, these are
the interactions between the bean and the container in which it has been deployed.
This is a recurrent theme throughout the EJB architecture, so it is important to fully
understand it. The methods you define in your bean will be invoked either by its client or
by the EJB container itself. Specifically, the methods invoked by the client will be those
defined in the remote interface, whereas the methods invoked by the container are those
defined by the javax.ejb.SessionBean interface. The bean must also provide methods
that correspond to the create method of the bean’s home interface.
Figure 5.2 shows the SessionBean interface and its super-interfaces.
FIGURE 5.2
The javax.ejb.
SessionBean interface
defines certain lifecycle methods that must
be implemented by
Session beans.
interface
java.io.Serializable
!
interface
EnterpriseBean
interface
SessionBean
+setSessionContext(sessioncontext:SessionContext):void
+ejbRemove():void
+ejbActivate():void
+ejbPassivate():void
In the case study, the AgencyBean class indicates that it is a Session bean implementation
by implementing this interface:
package agency;
import javax.ejb.*;
// some import statements omitted
public class AgencyBean implements SessionBean
{
// code omitted
}
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 170
170
Day 5
The lifecycle for Session beans, as perceived by the Session bean and as likely to be
enacted by the EJB container, is as shown in the UML state chart diagram in Figure 5.3.
FIGURE 5.3
Stateless Session beans
have a lifecycle managed by the EJB container.
[pool too small]/
setSessionContext
Context Set
[context set]/ejbCreate
create^ejbobject.new()
remove^ejbobject.finalize()
Pooled
[surplus]/ejbRemove
[business method invoked]
[method completes]
Bound to client
business method
The lifecycle is as follows:
• If the EJB container requires an instance of the stateless Session bean (for example, because the pool of instances is too small), it instantiates the bean and then
calls the lifecycle method setSessionContext(). This provides the bean with a
reference to a SessionContext object, providing access to its security and transaction context.
• Immediately after the context has been set, the container will call ejbCreate().
This means that the bean is now ready to have methods invoked. Note that the
ejbCreate() method is not part of the SessionBean interface, but nevertheless
must be declared in the bean.
• When a client invokes a business method, it is delegated by the bean’s EJBObject
proxy to the bean itself. During this time, the bean is temporarily bound to the
client. When the method completes, the bean is available to be called again.
The binding of the bean to the client lasts only as long as the method takes to execute, so
it will typically be just a few milliseconds. The EJB specification specifies this approach
so that the bean developer does not need to worry about making the bean thread-safe.
07 0672323842 CH05
3/20/02
9:36 AM
Page 171
Session EJBs
171
To support the case where two (or more) clients need to invoke the service of some stateless Session bean at the same time, most EJB containers hold a pool of Session beans. In
general, the pool will never be larger than the maximum number of concurrent clients. If
a container decides that the pool is too large or that some Session bean instances are surplus, it will call the bean’s ejbRemove() method.
Note
As you will see on Day 12, “Servlets,” servlets have similarities with stateless
Session beans. However, in the servlet specification, they are defined to
work in precisely the opposite way; by default, a servlet must be threadsafe, and there is only one instance of it.
If the client calls create() or remove(), the bean itself is not necessarily affected.
However, the client’s reference to the bean will be initialized (or destroyed). The client is
not aware of the complexities of this lifecycle, so the client’s perception of a stateless
bean is somewhat simpler, as shown in Figure 5.4.
FIGURE 5.4
The client’s perception
of the bean’s lifecycle
is simple.
deploy/setSessionContext
Context Set
create/ejbCreate
Ready
remove/ejbRemove
business method
From the client’s perspective, the bean is simply instantiated when the client calls
create() on the bean’s home interface, and is removed when the bean calls remove() on
the bean itself.
5
07 0672323842 CH05
3/20/02
172
9:36 AM
Page 172
Day 5
Note
The EJB specification does not attempt to prescribe too closely the implementation of EJB containers, and correctly focuses instead on their specification. Unfortunately, it does not always identify the only realistic implementation.
For example, the EJB specification suggests that the EJB container is at liberty to adopt any appropriate pooling policy for Session beans. In Figure 5.3,
you saw the state chart for a container using an eager instantiation policy,
pre-instantiating beans before they are necessarily used. However, the fact
that beans can throw CreateException exceptions from their ejbCreate()
method seems to imply that only a lazy instantiation policy—instantiating
beans only as they are required—could be used.
In fact, it is the case that some EJB containers do not maintain a pool of
Session bean references and, instead, simply instantiate beans as required. In
other words, the actual lifecycle for the bean matches that perceived by the
client. While this might seem a wasteful approach, in fact it is not; modern
JVMs are becoming so efficient that maintaining a pool of beans is more
expensive than simply instantiating beans as needed.
Figures 5.3 and 5.4 show how the methods of the SessionBean interface are invoked
through the bean’s lifecycle. You will have noticed that the ejbActivate() and
ejbPassivate() methods are not mentioned; this is because these methods are only
called for stateful Session beans, a topic covered later today. However, given that these
methods are in the SessionBean interface, they do require an implementation. For stateless Session beans, this implementation will be empty.
The implementation of the lifecycle methods is covered later today in the “Implementing
a Stateless Session Bean” section.
Specifying a Stateless Session Bean
As you will by now have gathered, the responsibilities of Session beans (and indeed,
Entity beans) are specified through its remote and home interfaces. These are what the
EJB container makes available to the remote clients.
To define a home interface for a stateless Session bean, extend javax.ejb.EJBHome. To
define a remote interface, extend javax.ejb.EJBObject. Because both EJBHome and
EJBObject extend the java.rmi.Remote interface, the rules for remote objects (in the
Java sense of the word) must be followed.
The following is the home interface for the Agency session bean. If it looks familiar, it
should be—you saw this for the first time just yesterday.
07 0672323842 CH05
3/20/02
9:36 AM
Page 173
Session EJBs
173
package agency;
import java.rmi.*;
import javax.ejb.*;
public interface AgencyHome extends EJBHome
{
Agency create() throws RemoteException, CreateException;
}
The AgencyHome interface defines a single no-arg method called create(). This method
returns an Agency, which is the remote interface for the Agency bean. Because this
remote interface is remote (that is, extends java.rmi.Remote), what the client that calls
this interface will receive is a reference to the remote Agency object. In other words, the
client will obtain an RMI stub to the Agency.
The EJB specification requires that stateless Session beans must define this single no-arg
version of the create() method. The bean can perform any initialization it requires
there. The create() method throws java.rmi.RemoteException, as required for remote
objects, and also throws javax.ejb.CreateException. This is an exception that the bean
can throw to indicate that it was unable to initialize itself correctly.
The create() method in the home interface implies a corresponding ejbCreate()
method in the bean class itself. This delegation to a method with an ejb prefix is prevalent throughout the EJB specification, so you will become quite familiar with it over the
next few days. The corresponding code in the AgencyBean class is as follows:
package agency;
// some import statements omitted
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
public class AgencyBean implements SessionBean
{
public void ejbCreate() throws CreateException {
// implementation omitted
}
// code omitted
}
Note that the ejbCreate() method also takes no arguments because the argument list
must match. The throws clause includes javax.ejb.CreateException, because that was
defined in the home interface, but does not include java.rmi.RemoteException. This is
because the bean itself is not remote; it is the code generated by the vendor’s deployments tools that is remote. The EJB specification requires also that ejbCreate() method
returns void.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 174
174
Day 5
Listing 5.1 shows the remote interface for the Agency session bean. Again, you saw this
yesterday:
LISTING 5.1
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
Remote Interface for the Stateless Agency Bean
package agency;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
public interface Agency extends EJBObject
{
String getAgencyName() throws RemoteException;
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30: }
Collection findAllApplicants() throws RemoteException;
void createApplicant(String login, String name, String email)
throws RemoteException, DuplicateException, CreateException;
void deleteApplicant (String login)
➥throws RemoteException, NotFoundException;
Collection findAllCustomers() throws RemoteException;
void createCustomer(String login, String name, String email)
throws RemoteException, DuplicateException, CreateException;
void deleteCustomer(String login)
➥throws RemoteException, NotFoundException;
Collection getLocations() throws RemoteException;
void addLocation(String name)
➥throws RemoteException, DuplicateException;
void removeLocation(String code)
➥throws RemoteException, NotFoundException;
Collection getSkills() throws RemoteException;
void addSkill(String name)
➥throws RemoteException, DuplicateException;
void removeSkill(String name)
➥throws RemoteException, NotFoundException;
List select(String table) throws RemoteException;
You can see that the Agency Session bean provides a number of sets of functionality, managing applicants, customers, locations, and skills. These services manipulate data within
the database, but there is no underlying state for the bean itself. In each case, the methods
throw java.rmi.RemoteException as required and also throw various other exceptions.
The DuplicateException and NotFoundException are user-defined exception classes that
simply extend java.lang.Exception. You encountered these classes yesterday.
07 0672323842 CH05
3/20/02
9:36 AM
Page 175
Session EJBs
175
For each of these methods in the remote interface, there is a corresponding method in
the Session bean. As was noted before, this is not because the bean has implemented the
remote interface (it hasn’t) but because the EJB specification requires it so that the
EJBObject proxy (the vendor-generated implementation of the remote interface) can delegate to the bean. The business methods for the AgencyBean have the same signature as
those in the remote interface, with the exception that they do not throw
java.rmi.RemoteException. Those are the steps to specifying a stateless Session bean’s
interface. Indeed, as you will see later today and tomorrow, specifying the interface of
stateful Session beans and of Entity beans follows along very similar lines. In the next
section, “Implementing a Stateless Session Bean,” you will see the implementation of
some of these methods.
Implementing a Stateless Session Bean
Implementing a Session bean involves providing an implementation for the methods of
the javax.ejb.SessionBean, corresponding methods for each method in the home interface, and a method for each method in the remote interface.
Implementing javax.ejb.SessionBean
The implementation of the methods of the SessionBean interface is often boilerplate. The
setSessionContext() method usually just saves the supplied SessionContext object:
private SessionContext ctx;
public void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
Although ejbRemove() method is part of the SessionBean interface, you’ll learn about
its implementation in the next section. As already noted, for a stateless Session bean, the
ejbActivate() and ejbPassivate() methods should have a null implementation:
public void ejbActivate() { }
public void ejbPassivate() { }
Implementing the Home Interface Methods
The home interface has a single method create(). The ejbCreate() method in the bean
corresponds to this method. It makes sense to look up JNDI references in the
ejbCreate() method and store them in instance variables. This is shown in Listing 5.2.
LISTING 5.2
AgencyBean.ejbCreate() Method
1: private DataSource dataSource;
2: private String name = “”;
3: public void ejbCreate () throws CreateException {
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 176
176
Day 5
LISTING 5.2
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19: }
Continued
InitialContext ic = null;
try {
ic = new InitialContext();
dataSource = (DataSource)ic.lookup(“java:comp/env/jdbc/Agency”);
}
catch (NamingException ex) {
error(“Error connecting to java:comp/env/jdbc/Agency:”,ex);
return;
}
try {
name = (String)ic.lookup(“java:comp/env/AgencyName”);
}
catch (NamingException ex) {
error(“Error looking up java:comp/env/AgencyName:”,ex);
}
Tip
On Day 18, “Patterns,” you will learn about a design pattern that simplifies
JNDI lookups. It can also speed up your beans; some EJB containers are not
particularly efficient at obtaining references from within JNDI.
In this case, the ejbCreate() method makes two lookups from JNDI; the first is to
obtain a DataSource (you will see how this is used shortly) and the other is to obtain
environment configuration information—that is, the name of the agency—from the
deployment descriptor. You will learn about the deployment descriptor in the following
section.
Incidentally, this is a good place to note that stateless Session bean does not mean that
the bean has no state; just that it has no state that is specific to any given client. In the
case of the Agency bean, it caches a DataSource and its name in instance variables.
The home interface inherits a remove(Object o) from EJBHome. This corresponds to the
method of the bean. The implementation is pretty simple; it should just
reset state:
ejbRemove()
public void ejbRemove(){
dataSource = null;
}
07 0672323842 CH05
3/20/02
9:36 AM
Page 177
Session EJBs
Note
177
The ejbRemove() method is mandated by the EJB specification in three different ways! It appears in the SessionBean interface, and it is required as
the method corresponding to the home method of remove(Object) (inherited from javax.ejb.EJBHome) and is also required as the method corresponding to the remote method of remove() (inherited from
javax.ejb.EJBObject). It is covered here because it fits best along side the
coverage of ejbCreate().
Implementing the Remote Interface Methods
The remaining methods correspond to the business methods defined in the remote interface. The Agency session bean manipulates the data in the Applicant, Customer,
Location, and Skill tables, providing methods to return all the data in a table, to insert
a new item, or to delete an existing item. When deleting rows, rows in dependent tables
are also removed.
The methods that manipulate the database all require a java.sql.Connection to submit
SQL to the database. In regular “fat client” applications, the idiom is to create a database
connection at application startup and to close the connection only when the user quits the
application. This idiom exists because making database connections is expensive in performance terms. When writing EJBs, however, the idiom is the precise opposite. You
should obtain the database connection just before it is needed, and close it as soon as
your processing is complete. In other words, “acquire late, release early.” This is because
the EJB container has already made the database connections and holds them in a pool.
When your bean obtains its connection, it is simply being “leased” one from the pool for
a period of time. When the connection is “closed,” in reality it is simply returned back to
the pool to be used again.
The getLocations() method shows this principle clearly, as shown in Listing 5.3.
LISTING 5.3
AgencyBean.getLocations() Method
1: public Collection getLocations() {
2:
Connection con = null;
3:
PreparedStatement stmt = null;
4:
ResultSet rs = null;
5:
try {
6:
con = dataSource.getConnection();
7:
stmt = con.prepareStatement(“SELECT name FROM Location”);
8:
rs = stmt.executeQuery();
9:
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 178
178
Day 5
LISTING 5.3
Continued
10:
Collection col = new TreeSet();
11:
while (rs.next()) {
12:
col.add(rs.getString(1));
13:
}
14:
15:
return col;
16:
}
17:
catch (SQLException e) {
18:
error(“Error getting Location list”,e);
19:
}
20:
finally {
21:
closeConnection(con, stmt, rs);
22:
}
23:
return null;
24: }
25: private void closeConnection (Connection con,
26:
PreparedStatement stmt, ResultSet rslt) {
27:
if (rslt != null) {
28:
try {
29:
rslt.close();
30:
}
31:
catch (SQLException e) {}
32:
}
33:
if (stmt != null) {
34:
try {
35:
stmt.close();
36:
}
37:
catch (SQLException e) {}
38:
}
39:
if (con != null) {
40:
try {
41:
con.close();
42:
}
43:
catch (SQLException e) {}
44:
}
45: }
In this method, you can see the DataSource object obtained in the ejbCreate() method
in use. The Connection object is obtained from this DataSource object. Another advantage of this approach is that the user and password information does not need to be
embedded within the code; rather, it is set up by the deployer who configures the
DataSource using vendor-specific tools. As you remember from Day 2, “The J2EE
Platform and Roles,” in the J2EE RI, the DataSource object is configured by editing the
resource.properties file in the %J2EE_HOME%\config directory.
The other business methods all access the database in a similar manner.
07 0672323842 CH05
3/20/02
9:36 AM
Page 179
Session EJBs
179
Exceptions
Your bean needs to be able to indicate when it hits an exception. The EJB specification
lays out certain rules as to the types of exceptions your bean can throw, because the
client does not call your bean directly. For remote clients, there is also the possibility of
network problems.
The EJB specification categorizes exceptions as either application exceptions or system
exceptions. These correspond quite closely to the regular Java categories of checked
exceptions and runtime exceptions.
Figure 5.5 shows the exceptions in the javax.ejb package, indicating which are application and which are system exceptions.
FIGURE 5.5
Exceptions are either
system exceptions or
application exceptions.
0..1
caused by
java.lang.Exception !
System
Exceptions
java.io.IOException !
java.lang.RuntimeException !
java.rmi.RemoteException !
EJBException
-causeException:Exception
+EJBException()
+EJBException(message:String)
+EJBException(ex:Exception)
+EJBException(message:String,ex:Exception)
+getCausedByException():Exception
+getMessage():String
+printStackTrace(ps:PrintStream):void
+printStackTrace():void
+printStackTrace(pw:PrintWriter):void
NoSuchEntityException
AccessLocalException
CreateException
DuplicateKeyException
FinderException
RemoveException
ObjectNotFoundException
Application
Exceptions
NoSuchObjectLocalException
So, what do these categorizations mean? If a bean throws an application exception, the
EJB container will propagate this back to the application client. As you shall see on Day
8, “Transactions and Persistence,” any ongoing transaction is not terminated by an application exception. In other words, the semantics of an application exception are pretty
similar to a checked exception; generally, the client can recover if desired.
However, if a bean throws a system exception, that indicates a severe problem that will
not be recoverable by the client. For example, if the bean has been incorrectly deployed
such that the database connection fails, there is very little that the client can do about it.
5
07 0672323842 CH05
180
3/20/02
9:36 AM
Page 180
Day 5
In such a case, the EJB container will take steps to terminate any ongoing transaction
because it is unlikely to complete. Moreover, the EJB container will discard the bean
instance that threw the exception. In other words, there is no need to code any clean up
logic in your bean after having thrown a system exception.
Although all runtime exceptions are classified as EJB system exceptions, the
javax.ejb.EJBException is a RuntimeException provided for your use. This class
allows the underlying cause to be wrapped through one of its constructors. The error()
helper method in AgencyBean does precisely this:
private void error (String msg, Exception ex) {
String s = “AgencyBean: “+msg + “\n” + ex;
System.out.println(s);
throw new EJBException(s,ex);
}
In Figure 5.5, you can see that there is one checked exception, namely
java.rmi.RemoteException, that is classified as an EJB system exception rather than as
an EJB application exception. Your bean code should never throw this exception; instead,
it is reserved for the EJB container itself to throw. If your bean has hit a system exception, it should throw an EJBException rather than RemoteException.
Configuring and Deploying a Stateless
Session Bean
With the code compiled, the next step is to deploy the bean onto the EJB container.
As you learned yesterday, EJBs are designed to be portable across EJB containers, and
the configuration information that defines the bean’s name, interfaces, class(es), characteristics, dependencies, and so on is stored in an XML document called a deployment
descriptor. This is provided along with the bean code itself.
As you appreciate from Day 2, there are several EJB roles involved in building the
deployment descriptor. The bean provider specifies the information about a given bean
(“intra-bean” configuration information, if you like), and the application assembler specifies the information about all the beans in an application (“inter-bean” configuration
information). When both the bean deployer and application assembler have specified
their information, the deployment descriptor is complete.
However, that’s not the end of the story, because the deployment descriptor does not
define every piece of configuration information necessary to deploy a bean. In effect, the
deployment descriptor defines only the logical relationships and dependencies between
the beans. There will also be additional configuration information that maps the logical
07 0672323842 CH05
3/20/02
9:36 AM
Page 181
Session EJBs
181
dependencies of the deployment descriptor to the physical environment. Performing this
mapping is the role of the deployer.
EJB container vendors are free to capture this additional mapping in any way they want,
although most use auxiliary deployment descriptors, again usually XML documents. In
the case of the J2EE RI, the auxiliary deployment descriptors are indeed XML documents. The EJB specification explicitly disallows vendors from storing their auxiliary
mapping information in the standard deployment descriptor itself.
Thus, to port an EJB from one EJB container to another, all that should be required is to
recreate an auxiliary deployment descriptor. In other words, the deployer has to redeploy
the application, but the bean provider and the application assembler should not have to
get involved.
Because manipulating XML documents can be somewhat error prone, most EJB container vendors provide graphical tools to do the work. As you saw yesterday, this is the
deploytool GUI in the case of the J2EE RI. Unfortunately, many such tools do not distinguish between information that is being saved in the standard deployment descriptor
and that which is being saved in the vendor’s own auxiliary deployment descriptors.
Also, many tools do not explicitly support the EJB architecture’s concept of roles, making it possible for a bean provider to start specifying information that might more correctly be decided only by the application assembler or even the deployer. The J2EE RI
deploytool is guilty on both counts.
Because you may not be using J2EE RI in your own projects, this section presents the
task of deployment by looking at both the J2EE RI deploytool and also the underlying
XML deployment descriptor. Having a firm understanding as to how these relate should
make it much easier for you to deploy if you aim to deploy to some other EJB container.
It also has to be said that understanding the XML deployment descriptor makes the
deploytool GUI easier to comprehend.
Using deploytool
This section shows how to deploy the Day 5 version of the case study application to the J2EE
RI. It’s best if you follow along (but if you’re on a train, just read the text and make do).
As usual, start up the Cloudscape RDBMS and J2EE RI using two console windows.
Then, start up a third console window and start deploytool.
By choosing File, Open, load up the day05\Examples\agency.ear enterprise application
archive. This defines two groups of Session beans—Agency and Advertise. Their contents have already been configured to contain the appropriate code. Click either of these
in the explorer on the left side of the deploytool GUI and their contents will be shown
on the right side. Note that for both of these beans, some supporting classes (application
exception classes) also constitute part of the bean.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 182
182
Day 5
To deploy the Session beans, select the Examples item (under the Applications folder in
the explorer area on the left side) and choose the Tools, Deploy menu option. Click Next
twice and then click Finish.
As you saw yesterday, the deployment descriptor holds configuration information. This is
accessible within deploytool as follows. Select the Agency element from the explorer,
and then choose Tools, Descriptor Viewer from the menu. This will display the XML
deployment descriptor for all of the beans in that EJB JAR file (in this case, just the one
Agency bean). Figure 5.6 shows this screen.
FIGURE 5.6
The deploytool lets
you view the underlying deployment
descriptor.
In the following sections, you’ll see how this information is structured and built up.
Structural Elements
XML documents provide a mechanism to store data in a hierarchical format, and are
similar in style to HTML documents. You shouldn’t have too much trouble following the
coming discussion, but if you want to do some additional background reading, skip forward to Day 16, “Integrating XML with J2EE,” which covers XML documents in more
detail.
The format of the EJB deployment descriptor is defined by a document type definition
(DTD) file called ejb-jar_2_0.dtd in the %J2EE_HOME%\lib\dtds\ directory. The root
of an EJB deployment descriptor is the ejb-jar element, whose definition is as follows:
07 0672323842 CH05
3/20/02
9:36 AM
Page 183
Session EJBs
183
<!ELEMENT ejb-jar (description?, display-name?, small-icon?, large-icon?,
enterprise-beans, relationships?, assembly-descriptor?, ejb-client-jar?)>
This indicates that an ejb-jar element may (the ? sign suffix) contain one each of a
description, display-name, small-icon, large-icon, relationships, assemblydescriptor and ejb-client-jar elements, and must (no suffix) contain one
enterprise-beans element.
The enterprise-beans element’s definition is as follows:
<!ELEMENT enterprise-beans (session | entity | message-driven)+>
This states that an enterprise-beans element consists of one or many (the + sign suffix)
elements that are either session, entity, or message-driven elements. In other words,
the enterprise-beans element contains one or more session, entity, or messagedriven elements. You will learn about the session element shortly.
In Figure 5.6, you can see this structure in the deployment descriptor, and you can also
see the same hierarchy in deploytool’s explorer pane on the left side of the explorer.
The Advertise ejb-jar element consists of two beans.
Presentational Elements
In many of the definitions within the DTD, you will see the display-name,
description, small-icon, and large-icon elements defined. These are used by vendor
deployment tools when managing your beans, so you will certainly want to define a
display-name to distinguish the beans in the tool’s GUI. Whether you choose to provide
the remaining elements is up to you. Their presence is primarily so that third- party companies can develop EJBs to sell as “off-the-shelf” business logic components.
In Figure 5.6, you can see that the display-name element for the two groups of EJB
JARs have been set to Advertise and Agency. This is shown in the explorer on the left
side of deploytool GUI. It is also presented as the (read-only) JAR Display Name on
the right side when the Advertise node is selected. There doesn’t appear to be any good
reason why this is read-only in deploytool, that’s just the way the tool works.
If you wanted to add another EJB to the enterprise application, (using the File, New,
Enterprise Bean menu option) it would either be in an existing ejb-jar or a new ejbjar could be defined. When choosing the second option, the display-name for your new
collection of EJBs can be specified. This is shown in Figure 5.7.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 184
184
Day 5
FIGURE 5.7
The deploytool allows
EJBs to be defined in
either their own ejbjar (with attendant
deployment descriptor)
or in an existing ejbjar.
For your own custom applications, it really is up to you whether you choose to use one
ejb-jar or several. In the case study example, the latter has been used. Certainly, if you
wanted to use some off-the-shelf component EJBs bought from a third-party vendor, the
EJBs will already have been bundled into an EJB JAR file. To add these to your enterprise application, you would use File, Add to Application, EJB JAR menu option. When
the selected JAR file is read, the display-name element from the associated XML
deployment descriptor would then be used.
Session Element
The configuration information for Session beans is defined—not surprisingly—in the
session element of the DTD. Its definition is as follows:
<!ELEMENT session (description?, display-name?, small-icon?, large-icon?,
ejb-name, home?, remote?, local-home?, local?, ejb-class,
session-type, transaction-type,
env-entry*,
ejb-ref*, ejb-local-ref*,
security-role-ref*, security-identity?,
resource-ref*,
resource-env-ref*)>
This indicates that a session element may contain the presentational elements just discussed, and also will consist of a number of other mandatory and optional elements. The
mandatory elements are as follows:
07 0672323842 CH05
3/20/02
9:36 AM
Page 185
Session EJBs
185
• The ejb-name element is the mandatory logical name for the EJB. This must be
unique within all the EJBs defined within the ejb-jar element.
• The home, remote, local-home, and local elements define the remote and local
interfaces for the bean. They are all marked as being optional, although the specification also requires that either the home and remote and/or the local-home and
local elements are defined. As was noted previously, you’ll be learning about local
interfaces in detail tomorrow.
• The ejb-class element defines the class that has the bean’s actual implementation.
• The session-type element indicates whether the bean is stateless or stateful.
• The transaction-type element indicates how the EJB container should manage
transactions. You will be learning about this in detail on Day 8, so until then, just
specifying that transactions are Required will be sufficient.
This information is available graphically in deploytool. Select the Agency Session bean
in the explorer pane (under Agency ejb-jar). You should see something similar to that
shown in Figure 5.8.
FIGURE 5.8
The deploytool represents the underlying
deployment descriptor
graphically.
5
You should be able to see a pretty close resemblance between the information portrayed
in deploytool and the mandatory information required by the underlying deployment
descriptor. The only mandatory item not shown is transaction-type; that is on the
Transactions tab of the GUI.
07 0672323842 CH05
3/20/02
186
9:36 AM
Page 186
Day 5
The (fragment of the) underlying deployment descriptor for the Agency bean that is represented in Figure 5.8 is as follows:
<session>
<display-name>AgencyBean</display-name>
<ejb-name>AgencyBean</ejb-name>
<home>agency.AgencyHome</home>
<remote>agency.Agency</remote>
<ejb-class>agency.AgencyBean</ejb-class>
<session-type>Stateless</session-type>
… lines omitted …
</session>
This should tie in with the code that was presented earlier today.
Thus far, you have only seen the EJB standard deployment descriptor, but there is also
the vendor-specific deployment descriptor for J2EE. The structure of this auxiliary
deployment descriptor is defined by %J2EE_HOME%\lib\dtds\sun-j2ee-ri_1_3.dtd, but
it is not so necessary to learn its structure in detail because it all vendor specific, and
deploytool allows it to be configured through its GUI.
The auxiliary information in this descriptor maps the logical names to the physical runtime environment. For the Session bean itself, a mapping is required from its logical
ejb-name to its JNDI name. The following fragment from the auxiliary deployment
descriptor shows this:
<j2ee-ri-specific-information>
// lines omitted
<enterprise-beans>
// code omitted
<ejb>
<ejb-name>AgencyBean</ejb-name>
<jndi-name>ejb/Agency</jndi-name>
Figure 5.9 shows how deploytool portrays this information. Figure 5.9 also shows the
JNDI mappings for references; you will learn about these shortly.
The remaining optional items of the EJB deployment descriptor (env-entry, resourceand so on) also correspond to the different tabs of the deploytool window shown in
Figure 5.8. These each indicate different types of dependencies that the bean may have
with its runtime environment. The following sections discuss each in turn.
ref,
07 0672323842 CH05
3/20/02
9:36 AM
Page 187
Session EJBs
187
FIGURE 5.9
Behind the scenes,
deploytool stores the
JNDI mappings to an
auxiliary vendorspecific deployment
descriptor.
Environment Entries
Environment entries allow general configuration information—as might be found in a
.ini file or in the Windows Registry—to be made available to the bean. Such entries
are represented by the env-entry element in the deployment description and—not
surprisingly—are configured on the Env. Entries tab within deploytool.
The Agency bean uses an environment entry to look up its name. The relevant code is in
the ejbCreate() method:
InitialContext ic = new InitialContext();
// code omitted
name = (String)ic.lookup(“java:comp/env/AgencyName”);
The EJB specification requires that the EJB container makes the environment entries
available under the well-defined context of java:comp/env. Therefore, this is needed in
the JNDI lookup. However, this prefix is not required in the deployment descriptor itself.
The DTD defines the env-entry element as follows:
<!ELEMENT env-entry (description?, env-entry-name, env-entry-type, env-entryvalue?)>
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 188
188
Day 5
The type of the environment entry (String, Integer, and so on) is indicated through the
env-entry-type element. The actual value (env-entry-value) is optional, meaning that
the bean provider/application assembler does not need to define it. If the actual value
isn’t specified by these roles, the deployer will need to define the value.
Figure 5.10 shows this information being configured within deploytool.
FIGURE 5.10
Environment entries
can be managed
graphically by deploytool.
In the underlying deployment descriptor for the Agency bean, this corresponds to
<env-entry>
<env-entry-name>AgencyName</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>J2EE in 21 Days Job Agency</env-entry-value>
</env-entry>
To re-emphasize, note that the entry name is AgencyName, not
java:comp/env/AgencyName.
EJB References
Declaring EJB references for an EJB indicates that the bean being deployed depends on
other EJBs. Generally speaking, when there are inter-bean dependencies, both the dependent and dependee will have been written by the same bean provider. However, there will
be cases when this isn’t so. The issue then arises that the dependent EJB may not know
the deployed name of the EJB upon which it depends.
07 0672323842 CH05
3/20/02
9:36 AM
Page 189
Session EJBs
189
For example, a vendor might provide some sort of business-oriented bean (perhaps an
Invoice EJB) that can optionally perform logging through a Logging EJB. The same
vendor might well provide an implementation of a Logging EJB, but would also allow
for application assemblers to use some other EJB that implements the appropriate home
and remote interfaces. In this way, the application assembler could ensure that all logging
from beans within its application was consistent.
Now the Invoice EJB will have a reference to a Logging EJB in its JNDI lookup. This
might be something like the following:
InitialContext ic = new InitialContext();
Object lookup = ic.lookup(“ejb/Logger”);
LoggerHome home = (AgencyHome)PortableRemoteObject.narrow(lookup,
LoggerHome.class);
Logger logger = home.create();
The “ejb/Logger” string is hard-coded into the Invoice EJB source code and cannot be
changed by the application assembler or by the deployer. This is what is sometimes
referred to as the coded name or coded entry. However, the deployment descriptor allows
this logical name to be associated with an actual physical name through the ejb-ref elements.
The following is the ejb-ref element, as defined by the DTD:
<!ELEMENT ejb-ref (description?, ejb-ref-name, ejb-ref-type, home, remote, ejblink?)>
The ejb-ref-name element is the coded name—ejb/Logger in the previous example.
The names of the home and remote interfaces must be specified. Finally, the ejb-link
element specifies the actual ejb-name of the EJB that implements these interfaces. This
is a way for the application assembler to constrain the reference in the dependent EJB to
a particular EJB within the enterprise application.
In the Day 5 version of the case study, there are no EJB references between EJBs (you
will see some such references between EJBs tomorrow), but there are EJB references
from the clients and the EJBs. These are shown in Figure 5.11.
When EJB references are defined, they must also be mapped to the physical environment.
Figure 5.9 showed the mapping of EJBs and of references to JNDI names. These are shown
in the bottom half of the window of Figure 5.9. As an experiment, try temporarily creating
a EJB (remote) reference between the beans themselves, and then return to the JNDI tab in
deploytool (as shown in Figure 5.9). Here, the deployer should indicate the JNDI name
for the EJB that implements the remote interfaces. As required by the EJB specification,
deploytool validates that the JNDI name that is mapped by the deployer to the EJB reference is compatible with any ejb-link that may have been specified by the application
assembler. When you have finished experimenting, you should delete these EJB references.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 190
190
Day 5
FIGURE 5.11
There are EJB references from the clients
to the EJBs.
Resource References
Yesterday, you learned that the EJB container allows DataSources to be obtained via
JNDI. Within the deployment descriptor, a resource reference is used to define that
dependency of the EJB. The term resource reference is used instead of database reference because an EJB can depend on data resources other than databases. It could also
depend on an e-mail session (Day 11, “JavaMail”), on a URL, on a message topic or
queue factory (Day 10, “Message-Driven Beans”), or on a general resource as defined by
the Connector architecture (Day 19, “Integrating with External Resources”).
The Agency bean has a dependency on a DataSource reference that it refers to as
This can be seen in the ejbCreate() method of the AgencyBean code. As
with the environment entries, note that resource reference has been bound by the EJB
container under the context java:comp/env:
jdbc/Agency.
InitialContext ic = new InitialContext();
dataSource = (DataSource)ic.lookup(“java:comp/env/jdbc/Agency”);
Resource references are defined in the DTD as follows:
<!ELEMENT resource-ref (description?, res-ref-name, res-type, res-auth, ressharing-scope?)>
Going through these in turn
• The res-ref-name element is the coded name of the referenced resource.
• The res-type element is the fully-qualified interface or class name of the resource.
07 0672323842 CH05
3/20/02
9:36 AM
Page 191
Session EJBs
191
• The res-auth element indicates whether the container will provide the authentication information (username and password) or the application itself. In other words,
it indicates which overloaded version of DataSource.getConnection() will be
called—the one where username and password are supplied (Application authentication) or where they are not (Container authentication).
• The res-sharing-scope element indicates whether this resource can be shared
among beans (the default) or whether a separate resource will be set up for this
bean’s exclusive use.
So, in the deployment descriptor for the Agency bean, you will see the following:
<resource-ref>
<res-ref-name>jdbc/Agency</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
This information is also shown in deploytool, as shown in Figure 5.12.
FIGURE 5.12
Resource references
can be managed
graphically by deploytool.
5
Again, resource references must be mapped to physical resources. You defined the physical
resources for the case study on Day 2, when you edited the resource.properties file
within %J2EE_HOME%\config. The entries that are relevant to this discussion are as follows:
jdbcDataSource.5.name=jdbc/Agency
jdbcDataSource.5.url=jdbc:cloudscape:rmi:Agency
07 0672323842 CH05
192
3/20/02
9:36 AM
Page 192
Day 5
This instructs J2EE RI to create a DataSource using a URL of
jdbc:cloudscape:rmi:Agency and bind it into JNDI with a name of jdbc/Agency.
From the deployment descriptor, you can see that the declared resource reference in the
AgencyBean Session bean code is jdbc/Agency and is also mapped to a JNDI name of
jdbc/Agency. The fact that the logical and physical names are the same string strings can
be a source of confusion; the point is that both are needed. Moreover, the logical
resource reference could have been anything at all, so long as it is the same string that
appears in the code, in the standard ejb-jar.xml deployment descriptor and the vendorspecific auxiliary deployment descriptor.
The mapping between these two names is performed on the JNDI tab of deploytool, as
shown in Figure 5.9. You should be able to see there that, for example, the resource reference in the Agency bean called jdbc/Agency maps onto the JNDI name of jdbc/Agency.
The auxiliary deployment descriptor has the following entries:
<j2ee-ri-specific-information>
// lines omitted
<enterprise-beans>
// lines omitted
<ejb>
<ejb-name>AgencyBean</ejb-name>
// lines omitted
<resource-ref>
<res-ref-name>jdbc/Agency</res-ref-name>
<jndi-name>jdbc/Agency</jndi-name>
</resource-ref>
</ejb>
// remaining lines omitted
Resource Environment References
Resource environment reference entries allow access to so-called “administered objects.”
In J2EE RI, this means JMS queues or JMS topics. In future versions of J2EE, other
administered objects may well be specified.
Resource references and resource environment references sound very similar, and,
indeed, they are related. However, a resource reference is access to some sort of factory
object, used to manufacture (variously) database connections, URLs, JMS sessions, and
so on. On the other hand, an administered object must be defined up-front by an administrator and is persistent. The EJB specification doesn’t define RDBMS tables as administered objects, but it might well have. If it had, a resource reference would be to a database connection, and a resource environment reference might be to a table on the database to which you have connected.
The DTD defines resource environment references as follows:
<!ELEMENT resource-env-ref (
description?, resource-env-ref-name, resource-env-ref-type)>
07 0672323842 CH05
3/20/02
9:36 AM
Page 193
Session EJBs
193
The resource-env-ref-name is the name of the reference in the code, less the
java:comp/env prefix, and the resource-env-ref-type is either javax.jms.Queue or
javax.jms.Topic.
The Agency session bean does not have any dependencies on resource environment references, so there is no screen shot of the deploytool. However, you will be using resource
environment references on Day 10 when you work with the Java Messaging Service and
Message-driven beans. But (again) as an experiment, try adding an resource environment
reference on the Resource Env. Refs tab. On the JNDI tab (as shown in Figure 5.9), you
should be able to indicate the JNDI name for the administered object. When you have
finished experimenting, delete the resource environment reference.
Deploying the Enterprise Application
The enterprise application can be deployed from deploytool by using the Tools, Deploy
menu option. This causes the bean’s code components to be compiled and the proxy and
home interfaces to be generated and then packaged up into an ejb-jar using information
from the underlying deployment descriptor and auxiliary deployment descriptor. Finally,
the package is deployed across the network to the J2EE RI.
Once deployed, you can run your application. To do this, use
day05\agency\run\runAll.
Stateful Session Bean Lifecycle
Now that you have learned how to specify, implement, and deploy stateless Session
beans, it is time to look at stateful Session beans. As you shall see, there are many similarities (especially with regard to the deployment), but the lifecycle is different and warrants close attention.
The client’s view of the lifecycle of a stateful Session bean is identical to that of a stateless Session bean and, in truth, more closely matches the actual lifecycle as managed by
the container. Figure 5.13 shows this lifecycle.
From the client’s perspective, the bean is simply instantiated when the client calls
create() on the bean’s home interface, and it’s removed when the bean calls remove()
on the bean itself.
The bean’s viewpoint of its lifecycle is as shown in Figure 5.14.
The principle difference between stateful and stateless Session beans is the duration of
the time that the bean is bound to the client. With a stateless Session bean, the duration
was only as long as the time needed to execute the business method. With a stateful
Session bean, however, the bean stays bound until the client releases it. In this way, there
is a quite close correspondence between the client’s and the bean’s perspectives.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 194
194
Day 5
FIGURE 5.13
The client’s view of the
lifecycle of stateful
beans is identical to
that of stateless
Session beans.
deploy/setSessionContext
Context Set
create/ejbCreate
Ready
remove/ejbRemove
business method
FIGURE 5.14
A stateful Session
bean’s view of its lifecycle includes passivation and timeouts.
pool too small/setSessionContext
Pooled
[timeout]
[surplus]
create/ejbCreate
remove/ejbRemove
Bound to client
Passivated
[too many active]
/ejbPassivate
Ready
[business method or remove
invoked]/ejbActivate
business method
When the client calls create() on the home interface, a Session bean instance is
obtained. Most EJB containers maintain a pool of unbound Session bean instances, so
any unused instance will be chosen. This is then bound to the client. The client can call
business methods on the bean, and because the bean will remain bound, these can legitimately save to instance variables the state pertaining to the client. When the client is
done with the bean, it calls remove() which releases the bean back to the pool of
unbound instances.
07 0672323842 CH05
3/20/02
9:36 AM
Page 195
Session EJBs
195
The EJB specification uses the analogy of a shopping cart, and it is easy to see that
this is a natural fit. In such a case, the client would obtain a shopping cart bean using
create(), call methods such as addItem(), removeItem(), and checkout(), and then
release the bean using remove().
If there are many concurrent clients, the amount of memory to manage all of the clients’
state can become significant. Moreover, there is nothing to prevent a client from acquiring a bean, and then not using it—an abandoned shopping cart in the aisles, if you like.
The EJB specification addresses these issues by defining the notions of passivation and
of timeouts. Passivation allows the EJB container to release the memory used by a bean,
first writing out its internal state to some secondary storage. This is transparent to the
bean’s client; when the bean is next used, the EJB container first activates the bean. How
the EJB container actually implements passivation is not specified, but the specification
does require that the Session bean is serializable, so many implementations will take this
route and serialize the bean to a file. If a bean is not used for longer than its timeout, it
can be timed out and its memory released.
Note
Perhaps surprisingly, the EJB specification does not define how the timeout
for a Session bean reference is specified. Section 7.6.3 of the specification
indicates clearly that its definition is specific to the EJB container. Usually,
the information will be captured in a vendor deployment tool and stored in
an auxiliary deployment descriptor.
Equally, the EJB specification does not indicate how the EJB container should
decide when to passivate beans (though it does suggest that a “least recently used” strategy can be employed, see section 7.6).
Figure 5.14 showed the bean’s viewpoint of its lifecycle, but the actual lifecycle as managed by the EJB container is likely to be different again. After all, the whole point of
passivation is to reduce the number of bean instances; if the bean was merely “put to
sleep,” that wouldn’t have been accomplished, so Figure 5.15 shows the actual lifecycle
used by many EJB container implementations.
When the EJB container passivates a bean, its state is written out to secondary storage.
The bean instance is then destroyed. If a client whose bean instance has been passivated
invokes a method, the EJB container first re-instantiates the bean by deserializing it from
secondary storage. The business method is then invoked.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 196
196
Day 5
FIGURE 5.15
[pool too small]
/setSessionContext
[surplus]
The actual lifecycle of
stateful Session beans
as managed by the
EJB container is somewhat more complex.
Pooled
create/ejbCreate
[business method
invoked && !
timed out]
[state written]
Passivating
do/'write out state to secondary storage'
[timeout]
Activating
do/'restore state from secondary storage'
remove/ejbRemove
[too many active]
/ejbPassivate
Bound to client
[state restored]/ejbActivate
business method
Specifying a Stateful Session Bean
Specifying a stateful Session bean is similar to specifying a stateless Session bean. The
remote interface defines access to the bean by remote clients, and every method of the
remote interface must throw an RemoteException. The primary difference (from a specification viewpoint) is that there can be multiple create() methods in the home interface.
You will recall that a stateless Session bean allows only for a single create() method in
the home interface, and this corresponds to the ejbCreate() method of the bean itself.
For a stateful Session bean, the create() method can be overloaded, so that the stateful
bean can be given some initial state. From the client’s viewpoint, this is somewhat analogous to invoking a constructor on the bean.
For example, the Advertise bean in the case study is stateful. It represents an advertiser
of job positions. The home interface for this bean is as follows:
package agency;
import java.rmi.*;
import javax.ejb.*;
public interface AdvertiseHome extends EJBHome
07 0672323842 CH05
3/20/02
9:36 AM
Page 197
Session EJBs
197
{
Advertise create (String login) throws RemoteException, CreateException;
}
Obviously, the create() method has a corresponding ejbCreate() method in the
AdvertiseBean class itself. This ejbCreate() method must instantiate the bean with any
appropriate state, as shown in listing 5.4.
LISTING 5.4
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:
AdvertiseBean.ejbCreate() Method
package agency;
// imports omitted
public class AdvertiseBean implements SessionBean
{
private String login;
private String name;
private String email;
private String[] address;
private Collection jobs = new TreeSet();
public void ejbCreate (String login) throws CreateException {
this.login = login;
// database detail not shown
name = …;
email = …;
address[0] = …;
address[1] = …;
jobs = new TreeSet();
while(…) {
jobs.add( … );
}
}
}
Alternatively, the EJB specification allows for methods named createXXX() to be
defined in the home interface, with corresponding methods ejbCreateXXX(). These
methods can take parameters if required. Whether you choose to use this facility or just
use regular overloaded versions of create()/ejbCreate() is up to you.
Other than this one change of being able to pass in state in the create() method, there
really is little difference in the specification of a stateful bean compared to that of a stateless Session bean. The remote interface of the stateful Advertise Session bean is shown
in Listing 5.5.
5
07 0672323842 CH05
3/20/02
9:36 AM
Page 198
198
Day 5
LISTING 5.5
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
The Remote Interface for the Stateful Advertise Bean
package agency;
import java.rmi.*;
import javax.ejb.*;
public interface Advertise extends EJBObject
{
void updateDetails (String name, String email, String[] Address)
➥throws RemoteException;
String getName() throws RemoteException;
String getEmail() throws RemoteException;
String[] getAddress() throws RemoteException;
String[] getJobs() throws RemoteException;
15:
16: }
void createJob (String ref) throws RemoteException,
➥DuplicateException, CreateException;
void deleteJob (String ref) throws RemoteException, NotFoundException;
The ejbCreate() method from the home interface supplies the information to the bean
so that it can retrieve the data about the advertiser from the database. The remote interface allows this information to be accessed and be updated.
Implementing a Stateful Session Bean
When implementing a stateful Session bean, there are a number of issues that you must
keep in mind. They are discussed in this section.
Passivation
Unlike stateless Session beans, stateful Session beans are at liberty to store clientspecific state information in instance variables. However, because your bean may be passivated, any instance variables you define must be either primitives, references to serializable objects, or—failing that—be transient.
Of course, any transient variables will be reset to null if the bean is passivated and
then re-activated, so your implementation will need to deal with this. Classes that are not
serializable often depend in some way on the environment, such as an open
java.net.Socket, so that your bean can act as a network client to some service. The
general approach to dealing with this is to store other data that is serializable in instance
variables during the ejbPassivate() method. Then, the non-serializable reference can
be re-instantiated in the ejbActivate() method using this other data.
07 0672323842 CH05
3/20/02
9:36 AM
Page 199
Session EJBs
199
For example, in the case of a Socket, your bean could hold a String and an int representing the hostname and the port number of the socket. The instance variables and
ejbActivate() method could would be something like the following:
import java.net.*;
// code omitted.
private transient Socket clientSocket;
private String socketHost;
private int socketPort;
public void ejbActivate() {
this.clientSocket = new Socket(socketHost, socketPort);
}
Although your Session bean must itself be serializable, it is not necessary to explicitly
implement the java.io.Serializable interface. This is because the
javax.ejb.SessionBean interface extends from Serializable (by way of its superinterface, javax.ejb.EnterpriseBean).
Tip
Given that passivation causes quite a few implementation headaches, some
commentators have asked why the EJB specification goes to such lengths to
define a passivation mechanism. After all, operating systems are very good
at paging memory to disk, and this is all that the “secondary storage” is
really accomplishing.
These are good questions, with no ready answers. But if you would rather
have the operating system do the work and keep your beans simple, just
configure your beans with a very high passivation threshold.
You may be wondering how to handle passivation in a stateful Session bean that has a
reference to a home or remote interface of another EJB, a javax.sql.DataSource or
some other resource connection factory. After all, none of these references may be serializable. Luckily, though, the EJB specification puts the onus for worrying about these references on the EJB container (section 7.4.1). In other words, your bean is at liberty to
hold references to any of these types of objects, and you don’t need to care whether they
are serializable. Of course, most EJB container vendors are likely to comply with this
part of the specification by making sure that these objects are serializable.
Timeouts
Another difference between stateful and stateless Session beans is that stateful Session
beans may timeout. If a bean is timed out, the client’s reference is no longer valid, and any
further invocations on that reference will result in a java.rmi.NoSuchObjectException for
remote clients.
5
07 0672323842 CH05
3/20/02
9:37 AM
Page 200
200
Day 5
You should note from Figure 5.14 that if a bean times out, its ejbRemove() method will
not be called. This means that you shouldn’t adopt a convention of acquiring external
resources in ejbCreate() with a view to releasing them in ejbRemove(). Even releasing
the resources in ejbPassivate() is not enough, because a bean can be timed out even
from its ready state.
Caution
Don’t confuse passivation and timeout. An EJB container might implement
passivation using an LRU strategy and allow a bean timeout to be specified
in seconds. If the EJB container is not busy, a bean will not be passivated
according to the LRU strategy, but it may hit its timeout nevertheless.
Chaining State
It is generally a bad idea to have more than one stateful Session bean involved in any
conversation, because no matter which way you cut it, there’s always the chance that one
of them will time out, preventing the other from completing.
To see this, suppose the client calls stateful Session A, which, in turn, uses the services
of stateful Session B. There are two cases—the timeout of A is larger than that of B, or
the timeout is less than that of B. Taking each in turn
• Suppose that the timeout of Session bean A is 30 minutes and that of Session bean
B is 20 minutes. The client makes a call on A at time t1=0, which then calls B. If
the client calls A at time t2 = t1 + 25 minutes, A’s call to B will fail because B will
have timed out.
• Suppose now that the timeout of Session bean A is 20 minutes, and that of Session
bean B is 30 minutes. The client makes a call on A at time t1=0, which then calls
B. The client then calls A again at time t2 = t1 + 19 minutes, although for this call,
A does not need to call B to service the request. If the client calls A again at time
t3 = t2 + 19 minutes = t1 + 38 minutes, A’s call to B will fail because B was last
invoked more than 30 minutes ago and will have timed out.
A similar problem can occur with session beans and servlets; this is discussed on Day 12.
Configuring and Deploying a Stateful
Session Bean
Configuring and deploying a stateful Session bean is just the same as deploying a stateless Session bean. The only difference is that the session-type element in the deployment descriptor will be set to Stateful.
07 0672323842 CH05
3/20/02
9:37 AM
Page 201
Session EJBs
201
Client’s View
Yesterday, you saw how to use JNDI to obtain a reference to a Session bean home and
how to obtain a Session bean by calling the appropriate create() method. Now that you
have a full understanding of how Session beans work, there are a few other points that
are worth appreciating.
First, if your client has a reference to a stateless Session bean, although it should call
remove() when it is finished with the EJB, this method call doesn’t actually do particularly much. In particular, it won’t release any bean resources itself, as shown clearly by
the state chart diagrams in Figure 5.3. What this will do is allow the EJB container to
remove the EJBObject proxy for the bean.
Conversely, calling create() for a stateless Session bean doesn’t necessarily cause
to be called on the underlying bean, although the client will have a reference to an EJBObject after making this call.
ejbCreate()
One benefit of stateless beans over stateful is that they are more resilient. That is, if the
client invokes a method on a stateless bean and it throws an exception, the client can still
use their reference to try again. The client does not need to discard the reference and
obtain a new one from the home interface. This is because, behind the scene, the EJB
container will have discarded the bean that threw the exception, but can simply select
another bean from the pool to service the client’s retry attempt. This is safe to do because
the stateless Session beans have no state. Of course, it is possible that the retry attempt
might fail; it would depend on the underlying cause of the exception.
In contrast, if a stateful Session bean throws an exception, the client must obtain a new
Session bean reference and start its conversation over again. This is because the EJB container will have discarded the Session bean that threw the exception, discarding all the
client’s conversational state in the process.
Sometimes, a client may end up having two references to a Session bean. It may have
obtained them both from other method calls, for example. More precisely, the client will
have two RMI stubs to Session beans. If the client wants to determine whether these two
stubs refer to the same Session bean, it should not call the equals() method. This almost
certainly will not return true. Instead, the client should call isIdentical(EJBObject) on
the reference. This indicates whether both stubs refer to the same Session bean. Note that
two references to the same stateless Session bean will always return true, because—at
least conceptually—there is only a single instance (see EJB specification, section 7.5.6).
Earlier today, you saw the different types of exceptions that a bean can throw. If a bean
throws an application exception, the EJB container will propagate it back to the client. If
the bean throws an EJBException (representing a system exception), the EJB container
will throw a java.rmi.RemoteException in turn.
5
07 0672323842 CH05
3/20/02
9:37 AM
Page 202
202
Day 5
For the client, any RemoteException represents a severe problem. It doesn’t really matter
to the client if the RemoteException has arisen because of a network problem or because
of a problem with a bean. Either way, it will be unable to recover the problem.
Table 5.1 lists the system exceptions shown in Figure 5.5 and indicates how each is
raised and thrown. As you will see, the majority are raised when the EJB container itself
has detected a problem with either transactions or security. You will learn more about
transactions on Day 8, and more about security a week later on Day 15, “Security.”
TABLE 5.1
System Exceptions Are Thrown in a Variety of Situations
What
Event
Client Receives
Any bean
Throws javax.ejb.EJBException
(or any subclass)
RemoteException
BMP Entity bean
Throws NoSuchEntityException
java.rmi.
java.rmi.NoSuchObject
Exception
Container
When client invokes method on a
reference to a Session bean that no
longer exists
java.rmi.NoSuchObject
When client calls a method without
a transaction context
javax.transaction.
Exception
TransactionRequired
Exception
When client has insufficient security access
java.rmi.
AccessException
When transaction needs to be rolled back
javax.transaction.
TransactionRolledBack
Exception
If you are wondering what BMP Entity beans are, the phrase is an abbreviation of “beanmanaged persistence Entity beans.” You’ll be learning about those tomorrow.
Patterns and Idioms
You now know all the important theory behind writing Session beans, but it’s always
helpful to have one or two real-world insights into how to write them in practice.
Patterns (or more fully, “design patterns”) are documented pearls of wisdom. Idioms are
much the same thing, although they tend to be more lower-level and code-oriented.
On Day 18, many of the design patterns discussed piecemeal throughout the book will be
brought together to see how they apply to all aspects to the J2EE environment. Some of
07 0672323842 CH05
3/20/02
9:37 AM
Page 203
Session EJBs
203
those given here will be presented more fully. But for now, there are patterns and idioms
specific to writing session EJBs. Reading this section might save you some time.
Business Interface
One of the peculiarities of the EJB architecture is that there is no direct link between the
defined remote interface and the bean that provides the implementation. For example, the
remote interface Advertise is not implemented by AdvertiseBean. However, no link is
needed because the EJB specification declares that it is the vendor’s deployment tools
that are responsible for ensuring that every method defined in the remote interface has a
corresponding method in the bean, and that the required methods for the home interfaces
also exist.
However, this means that any mismatches between the interface and the bean’s implementation will be picked up not during compilation, but during deployment. From a
practical point of view, this can make debugging the problem harder. After all, you are
probably accomplished at reading compile errors and figuring out what the cause of the
problem is. But you won’t (at least initially) be familiar with the errors that the vendor’s
deployment tool throws up when it announces that your bean does not comply with the
EJB specification.
One idiom that solves this is to create an additional interface that defines just the business methods. This interface is sometimes called the business interface. Then, the bean
implements the business interface, while the remote interface for the bean extends that
business interface.
This hasn’t been done in the case study, so as not to complicate and confuse. However, it
would be simple enough to introduce a business interface. Figure 5.16 shows a UML
class diagram that illustrates this for the Advertise bean.
With this idiom, if there is a mismatch between the interface and the bean, it will be
picked up during compile time.
There is just one subtlety of which you must be aware. When applying this technique to
the remote interface of a bean, the methods in the business interface must all throw
java.rmi.RemoteException. This is because the vendor-generated EJBObject for the
remote interface must follow the rules of remote objects, so that every one of its public
methods throws the RemoteException. This applies also to the inherited methods of the
business interface. The AdvertiseBus interface is shown in Listing 5.6.
5
07 0672323842 CH05
3/20/02
9:37 AM
Page 204
204
Day 5
FIGURE 5.16
interface
AdvertiseBus
Defining a business
interface means that
the bean can implement that interface.
interface
java.rmi.Remote !
updateDetails
getName
getEmail
getAddress
getJobs
createJob
deleteJob
interface
EJBHome
interface
EJBObject
interface
com.mycompany.agency.AdvertiseHome
interface
com.mycompany.agency.Advertise
create
interface
SessionBean
com.mycompany.agency.AdvertiseBean
com.mycompany.agency.Advertise
com.mycompany.agency.AdvertiseHome
dataSource
login
name
email
address
jobs
ctx
loadJobList
error
closeConnection
ejbPassivate
ejbActivate
ejbRemove
setSessionContext
ejbCreate
updateDetails
getLogin
getName
getEmail
getAddress
getJobs
createJob
deleteJob
LISTING 5.6
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
AdvertiseBus Interface
package agency;
import javax.ejb.*;
import java.rmi.RemoteException;
public interface AdvertiseBus {
void updateDetails (String name, String email, String[] address)
➥ throws RemoteException;
String getName() throws RemoteException;
String getEmail() throws RemoteException;
String[] getAddress() throws RemoteException;
String[] getJobs() throws RemoteException;
void createJob (String ref) throws RemoteException,
➥ DuplicateException, CreateException;
void deleteJob (String ref)
➥throws java.rmi.RemoteException, NotFoundException;
}
Adapter
As you write your EJBs, you will quickly find yourself writing reams of “boilerplate”
code. For example, the setSessionContext() method almost always just saves the
07 0672323842 CH05
3/20/02
9:37 AM
Page 205
Session EJBs
205
session context to an instance variable. The ejbActivate() and ejbPassivate() methods often do nothing at all.
If you have written any GUI applications using AWT or Swing, you almost certainly will
have used the various Adapter classes in the java.awt.event package. For example, the
java.awt.event.WindowAdapter class provides empty implementations of the seven
methods in the java.awt.event.WindowListener interface.
Adapter classes can also provide common default functionality. For example, the
AbstractList class acts as an adapter to the List interface in the java.util package,
providing the majority of the implementation required. Although the List interface
defines 25 methods in total, the AbstractList class implements all but two of them.
Creating an adapter for your Session beans can save you time in the long run. You can
provide default implementations for many of the lifecycle methods, and can also provide
additional methods. For example, you might decide to provide a log() method that will
forward any log messages to some remote URL or to a logging database.
Coarse-Grained
Remote Session beans should offer coarse-grained services. In other words, the services
offered by a remote Session bean should do large(-ish) chunks of work. The overhead of
the network to use these beans then becomes much less significant.
There are a number of approaches for arranging this. One approach is to create value
object classes. These are serializable objects that encapsulate enough information for the
Session bean to provide some service. The client populates these value objects and then
sends them across the wire as part of the remote method call. The Session bean then
interrogates its copy of the value object to accomplish its work. You will learn about the
value object pattern and some related patterns more fully on Day 18.
The value object idea as described is to encapsulate enough data in an object such that
the Session bean can do a reasonable chunk of work, but the responsibility for figuring
out what that chunk of work is still resides with the Session bean. A natural extension to
this concept is to place that responsibility into the value object itself. In effect, the value
object represents the action or command to be invoked. Indeed, the name of this design
pattern is the Command design pattern.
Gotchas
As you start to implement your own Session beans, there’s bound to be a couple of
aspects that will trip you up. The following quick checklist of such “gotchas” should
keep you on the straight-and-narrow:
5
07 0672323842 CH05
3/20/02
9:37 AM
206
Page 206
Day 5
• When you look up resources from JNDI, you should use a string of the form
java:comp/env/XXX. However, in the deployment descriptor, only the XXX is needed; the java:comp/env prefix is implicit.
• Perhaps obvious, but don’t use ejb as a prefix for naming your business methods.
Names of that format are reserved for the EJB architecture callback methods.
• Don’t implement the remote interface in your bean! If you do so, your bean could
inadvertently return itself (Java keyword this) as a return type. If a client starts
invoking methods on this reference, it will bypass all of the EJB container’s transaction and security control that is managed within the EJBObject proxy to the
bean. Instead, use the business interface idiom mentioned earlier today.
• The EJBObject interface defines a getPrimaryKey() method; the EJBHome interface defines a remove(Object primaryKey) method. Attempting to call either of
these for a Session bean will immediately throw a RemoteException, so don’t do
it. They are there only for Entity beans, discussed tomorrow.
• You’ll learn more about transactions on Day 8, but for now, remember that you
should not perform work that updates a database in the ejbCreate or ejbRemove
method, or indeed the other ejbXXX() lifecycle methods. This is because the transaction context is undefined. See section 7.5.7 of the EJB specification for more
details.
• Don’t try to have a Session bean call itself through its own EJBObject; it won’t
work. This is prevented so that the bean developer does not need to worry about
multiple threads. In other words, Session beans are non-reentrant. Of course, your
bean can call methods on itself directly through its implicit this reference.
• An ejbCreate() is required for stateless Session beans. It isn’t in the
javax.ejb.SessionBean interface because stateful Session beans won’t necessarily have a no-arg create() method.
Summary
You’ve covered a lot of ground today. You’ve learned that there are stateless and stateful
Session beans, and each has their own lifecycle. You’ve seen in detail how to specify a
Session bean by defining its home and remote interfaces and how to implement a bean
by providing corresponding implementations for the methods in the home and remote
interfaces, as well as how to implement the lifecycle methods as defined in the
javax.ejb.SessionBean interface.
You’ve also learned in detail how the deployment descriptor provides configuration
information describing the bean’s characteristics and dependencies to the EJB container.
07 0672323842 CH05
3/20/02
9:37 AM
Page 207
Session EJBs
207
Additionally, you’ve seen that those dependencies are logical dependencies that must be
mapped by the EJB deployer role to the physical resources defined through vendorspecific auxiliary deployment descriptor.
Finally, you’ve learned about some common techniques, design patterns, and idioms that
can simplify your coding and that represent best practice.
Q&A
Q What sort of state can stateless Session beans have?
A Somewhat surprisingly, stateless Session beans can store state, but it must be independent of the client.
Q What is the prefix that will appear in all JNDI lookups?
A The java:comp/env context is guaranteed to exist in an J2EE environment.
Q How are EJB system exceptions different from regular Java exceptions?
A
RemoteExceptions can be caused by network problems, which, in the context of
distributed J2EE enterprise applications, represent a system-level rather than
application-level exception.
Q How is the timeout for a stateful Session bean defined?
A Surprisingly, the mechanism for specifying the timeout interval for a stateful
Session bean is not mandated in the EJB specification.
Exercises
The exercise starts with a version of Day 5’s job agency case study that already provides
a number of beans:
• There is a stateless Agency bean that returns lists of all applications, customers,
locations and skills in the database.
• There is a stateful Advertise bean that allows advertisers (of jobs) to update their
name, e-mail, and address, and to manage the jobs they have posted to the job
agency.
• There is a stateful AdvertiseJob bean that represents an advertised job. This
allows the description, location, and required skills to be maintained.
However, it does not define any bean for the potential job applicants at this point. What
is required is a Register bean that allows applicants to register themselves with the job
agency. The exercise is to implement the RegisterBean, define this new bean within the
supplied agency.ear enterprise application, configure the bean, deploy your bean to the
J2EE RI, and finally test with either RegisterClient or AllClients (supplied).
5
07 0672323842 CH05
3/20/02
9:37 AM
Page 208
208
Day 5
Under the day05\exercise directory, you will find a number of subdirectories, including
the following:
•
src
The source code for the EJBs and clients.
•
classes
•
build
•
jar
•
run
Directory to hold the compiled classes; empty.
Batch scripts (for Windows and Unix) to compile the source into the classes directory. The scripts are named compileXXX.
Holds agency.ear—the agency enterprise application. Also holds
agencyClient.jar, the client-side JAR file optionally generated when deploying
EAR. This directory also holds some intermediary JAR files that are used only to
create the previous two JAR files.
jar
Batch scripts (for Windows and Unix) to run the JARs. Use the files in the
directory.
The Register and RegisterHome interfaces have been provided for you, under the src
directory. For example, the Register interface is as follows:
package agency;
import java.rmi.*;
import javax.ejb.*;
public interface Register extends EJBObject
{
void updateDetails (String name, String email,
➥
String location, String summary, String[] skills)
➥
throws RemoteException;
String getLogin() throws RemoteException;
String getName() throws RemoteException;
String getEmail() throws RemoteException;
String getLocation() throws RemoteException;
String getSummary() throws RemoteException;
String[] getSkills() throws RemoteException;
}
Today’s exercise is to implement the RegisterBean, configure an appropriate deployment descriptor, deploy your bean to the J2EE RI, and then test with the
RegisterClient. The bean will need to be stateful.
If you need some pointers as to how to go about this, read on.
1. Create a RegisterBean.java file and place this in day05\exercise\src\agency.
2. Implement RegisterBean to support the Register and RegisterHome interfaces
supplied. Base your implementation on that of AdvertiseBean, if you want.
07 0672323842 CH05
3/20/02
9:37 AM
Page 209
Session EJBs
209
3. Compile the RegisterBean code and the other interfaces, using the
build\compileAgencySessionEjbs script. Note that this expects the JAVA_HOME
and J2EE_HOME environment variables to be set.
4. In deploytool, open up the existing enterprise application
(day05\exercise\jar\agency.ear). Then, add the your Register bean to the
existing Agency ejb-jar by using File, New, Enterprise Bean. Specify the contents
to include all the required class files.
5. Configure the deployment descriptor for the RegisterBean appropriately. The bean
will need to be stateful. You will need to specify resource references and JNDI
names for the RegisterBean; bind the bean to a name of ejb/Register.
6. For the RegisterClient application client, configure the EJB reference appropriately. This has a coded name of java:comp/env/ejb/Register to refer to the
RegisterBean.
7. Deploy your bean by selecting it and using Tools, Deploy. As you do this, you will
need to define the appropriate JNDI mappings (mapping the logical EJB references
to the physical runtime environment). Request a client JAR file to be created,
called agencyClient.jar, to reside in the JAR directory.
8. To test out your bean, compile AllClients using the
build\buildAllClientsClient script. Then run the client using run\runAll.
You may also have noticed that in the build directory there are several other scripts apart
from those to compile the source. In fact, these can be used to recreate the agency.ear
file using the deployment descriptors held in the dd directory. You will be learning more
about this approach tomorrow. For now, all that you need to know is that the agency.ear
file can be created automatically by running the bat\buildall script. It requires that the
RegisterBean class exist to run successfully. You can then use deploytool to manually
define and configure the RegisterBean within the EAR.
The solution to the exercise is under day05\agency.
5
07 0672323842 CH05
3/20/02
9:37 AM
Page 210
08 0672323842 CH06
3/20/02
9:30 AM
Page 211
WEEK 1
DAY
6
Entity EJBs
Yesterday, you learned about Session beans, and how they provide a service to
a specific client.
The major topics that you will be covering today are
• How Entity beans represent domain objects, providing services that can
be used by all clients
• Two types of Entity beans—bean-managed persistence (BMP) and
container-managed persistence (CMP)
• How EJBs can provide a local interface in addition to their remote interface
• Specifying, implementing, configuring, and deploying BMP Entity beans
• Configuring and deploying EJBs from the command line rather than
using a GUI
Overview
When building IT systems, the functionality required of the application must
be specified and the business objects within the domain must be identified.
08 0672323842 CH06
212
3/20/02
9:30 AM
Page 212
Day 6
In “traditional” client/server systems, the application’s functionality can be implemented
in the front-end application or perhaps using database stored procedures, and the domain
objects are usually tables within an RDBMS. In building an EJB-based system, the application’s functionality corresponds to Session beans, and the domain objects correspond
to Entity beans.
You learned yesterday that Session beans take on the responsibility of implementing the
application’s business functionality. There will still be a presentation layer to display the
state of those Session beans, but its detail is unimportant in the larger scheme of things.
In the same way, Entity beans take on the responsibility of representing the domain data.
There will still a persistent data store to manage the data, almost certainly an RDBMS,
but the Entity beans abstract out and hide the detail of the persistence mechanism.
The N-tier Architecture Revisited
On the very first day, you were introduced to n-tier architectures, with the business logic
residing in its own tier. With an EJB-based system, both Session and Entity beans are
objects, so the business logic could be reside in either of them. In practice, the business
logic will be split over both, but to make the correct decision, it is worthwhile analyzing
what is meant by that phrase “business logic.”
Business logic refers to the collection of rules, constraints, procedures and practices put
in place by the business users to conduct their business. Some of the rules and constraints cannot be changed by the business, due to the domain in which the business is
performed. For example, there could be legal constraints and obligations. The procedures
and practices represent the (one particular) way in which business users have chosen to
conduct business.
Rules and constraints generally apply across all applications. In other words, it doesn’t
matter what the business is trying to accomplish, they will still need to comply with such
rules and constraints. This sort of business logic is best implemented through Entity
beans, because Entity beans are domain objects that can be reused in many different
applications.
In the business world, procedures and practices are usually the expression of some sort of
application, so Session beans are the best vehicle to implement this type of business
logic. Indeed, introducing computerized systems often changes these procedures and
practices (hopefully for the better, sometimes for the worse) because computers make
available new ways of accomplishing tasks.
• Session beans should have the business logic of a specific application—in other
words, application logic. The functionality provided should allow the user to
accomplish some goal.
08 0672323842 CH06
3/20/02
9:30 AM
Page 213
Entity EJBs
213
• Entity beans represent domain objects and should have business logic that is
applicable for all applications—in other words, domain logic. Usually, this logic
will be expressed in terms of rules and constraints.
If there is any doubt as to where the functionality should be placed, it is safer to place it
with the Session bean. It can always be moved later if it is found to be truly re-usable
across applications.
Figure 6.1 shows a UML component diagram to illustrate that there are at least four logical layers in an EJB-based system. Normally, at least some of these layers will be on the
same physical tier.
FIGURE 6.1
EJBs separate out
business logic into
application and
domain logic.
«swing»
user interface
«session EJB»
application logic
«servlet»
user interface
«servlet»
user interface
«entity EJB»
domain logic
«database»
persistence layer
«session EJB»
application logic
Comparison with RDBMS Technology
It’s natural to compare Entity beans with relational databases, because there is a significant overlap in the objectives of both technologies.
If you like to think in client/server terms, you could think of Session beans as being an
extension of the “client”, and Entity beans as being an extension of the “server”. It’s
important to realize that many clients can share a given Entity bean instance at the same
time, just as many database clients can read some row from a database table at the same
time.
You can also think of Entity beans as a high-performance data cache. Most RDBMS’
store data pages or blocks in a cache so that the most commonly used rows in tables can
be read directly from memory rather than from disk. Although the EJB specification does
not require it, many EJB containers adopt a strategy such that Entity beans are also
cached, so the data that they represent can also be read directly from memory. The
advantage of the Entity bean cache over an RDBMS’ data cache is that the Entity beans
already have semantic meaning and can be used directly. In contrast, data read from an
RDBMS’ data cache needs to be reconstituted in some way before it can be used.
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 214
214
Day 6
Identifying Entities
At their simplest, Entity beans can correspond to nothing more complex than a row in a
database; any data that might reasonably be expected to exist in a relational database
table is a candidate. This makes examples of Entity beans easy to come by:
• A Customer Entity bean would correspond to a row in a customer table keyed by
customer_num. The list of contact phone numbers for that Customer (in a
customer_phone_number detail table keyed on (customer_num, phone_num) would
also be part of the Customer Entity bean.
• An Invoice Entity bean might correspond to data in the order and order_detail
tables.
• An Employee Entity bean could be persisted in an employee table. The employee’s
salary history might also be part of the Entity bean.
Identifying entities can be made easier if a proper discipline is adopted with relational
modeling of the database. Of course, many databases just evolve over time as developers
add tables to support new requirements. Ideally, though, there should be a logical database model and a physical database model. The former is usually captured as an Entity
relationship diagram (ERD) with entities, attributes, and relationships. Relational database theory defines a process called normalization and different normal forms that aim to
eliminate data redundancy. It is this stage at which the normalization rules are applied, to
get to third normal form (at least).
Tip
This isn’t a book on relational database design, but here’s a cute phrase that
you can use to get you to third normal form: “every non-key attribute
depends upon the key, the whole key, and nothing but the key (so help me
Codd!).” If you are wondering who Codd is, that’s Dr. Codd who in the early
1970s laid down the mathematical foundations for relational theory.
Converting a logical database model to a physical model is in many ways mechanical.
Every entity becomes a table, every attribute becomes a column, and every relationship is
expressed through a foreign key column in the “child” table.
These entities identified in logical data modeling are the very same concepts that should
be expressed as Entity beans. Moreover, one of the key “deliverables” from performing
relational analysis is the selection of the primary key—the attribute or attributes that
uniquely identify an instance. Entity beans also require a primary key to be defined,
and this is manifested either as an existing class (such as java.lang.String or
java.lang.Integer) or a custom-written class for those cases where the key is composite.
08 0672323842 CH06
3/20/02
9:30 AM
Page 215
Entity EJBs
215
The name often given to such primary key classes is something like BeanPK, although it can
be anything. You can think of the primary key as some object that identifies the bean.
Note
The requirement of a primary key class to identify Entity beans has led to
criticism —in particular, by vendors of object-oriented DBMS—that the technology is not particularly object-oriented. In an OODBMS, the object does
not need a primary key identifier; it is identified simply by its reference.
Nevertheless, there are some differences between relational entities and Entity beans.
Whereas relational modeling requires that the data is normalized, object modeling places
no such constraints. Indeed, not even first normal form (where every attribute is scalar)
needs to be honored. For example, a Customer Entity bean might have a vector attribute
called phoneNumbers, with a corresponding accessor method getPhoneNumbers() that
returns a java.util.List. In a physical data model, there would need to be a separate
table to hold these phone numbers.
Even with a solid logical data model to guide you, selecting Entity beans is not necessarily straightforward. In particular, choosing the granularity of the entities can be problematic. With the customer example given earlier, the customer_phone table doesn’t really
seem significant enough to be an Entity. It’s just the way in which vector attributes have
to be modeled in relational databases. But what of the invoices? After all, invoices are
sent to customers, and any given invoice relates only to the orders placed by a single customer. So perhaps invoices should be considered as just vector attributes of customers,
with a getInvoices() accessor method? On the other hand, many modelers would argue
that the concept of Invoice is significant enough in itself—with its own state, behavior,
and lifecycle—to warrant being represented as its own Entity bean.
Specifying the interfaces should help you decide which is the correct approach. If the
invoice entity really is significant, you will find that the customer’s interface will be bloated
with lots of invoice-related methods. At this point, you can tease the two entity objects apart.
Caution
If you read old text books on EJB design, you will find that the traditional
(pre EJB 2.0) advice for Entity beans is that they should be coarse-grained—
in other words, that data from several tables correspond to a single entity.
This advice arose because of a combination of factors relating to pre EJB 2.0
Entity beans, one in particular being that Entity beans had to be remote
(implement the java.rmi.Remote interface).
These factors are no longer true, so the advice is out of date. Fine-grained
Entity beans are perfectly feasible for an EJB container that supports the EJB
2.0 specification.
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 216
216
Day 6
The javax.ejb Package for Entity Beans
Yesterday, you saw the interfaces and classes in the javax.ejb package that related to
Session beans. Figure 6.2 shows the interfaces and classes relevant to Entity beans.
FIGURE 6.2
The javax.ejb interfaces and classes pertaining to Entity beans.
interface
EJBMetaData
interface
HomeHandle
getEJBHome
getHomeInterfaceClass
getRemoteInterfaceClass
getPrimaryKeyClass
isSession
isStatelessSession
getEJBHome
interface
EJBHome
interface
EJBLocalHome
remove
remove
remove
getEJBMetaData
getHomeHandle
interface
java.rmi.Remote
!
interface
EJBContext
getEJBHome
getEJBLocalHome
getEnvironment
getCallerIdentity
getCallerPrincipal
isCallerInRole
isCallerInRole
getUserTransaction
setRollbackOnly
getRollbackOnly
interface
Handle
interface
EnterpriseBean
getEJBObject
interface
EntityContext
interface
EJBObject
getEJBHome
getPrimaryKey
remove
getHandle
isIdentical
interface
EJBLocalObject
getEJBLocalObject
getEJBObject
getPrimaryKey
getEJBLocalHome
getPrimaryKey
remove
isIdentical
interface
EntityBean
setEntityContext
unsetEntityContext
ejbRemove
ejbActivate
ejbPassivate
ejbLoad
ejbStore
As you can see, many of the supporting classes are common, which is good news
because that means there’s less to learn. The principle differences are as follows:
• The Entity bean implements javax.ejb.EntityBean rather than
javax.ejb.SessionBean, so there is a different lifecycle.
• The Entity bean is initialized with an EntityContext rather than a
SessionContext. An EntityContext exposes a primary key to the Entity bean, a
concept not applicable to Session beans.
Other details of the javax.ejb interfaces are the same as for Session beans. Briefly,
the home and remote interfaces for the Entity bean are defined by extending EJBHome
and EJBObject, respectively, and the local-home and local interfaces by extending
EJBLocalHome and EJBLocalObject. You will be learning more about local interfaces later today, because they are highly relevant to implementing Entity beans.
08 0672323842 CH06
3/20/02
9:30 AM
Page 217
Entity EJBs
217
The EJBMetaData class provides access to the constituent parts of the Entity bean component, and the Handle and HomeHandle interfaces provide the ability to serialize a reference to a remote bean or home and then to re-instantiate this instance by deserializing
the handle. None of these interfaces is discussed further.
Entity Bean Types
Entity beans represent shared persistent data stored in an RDBMS or other persistent
data store. If the data store is relational, the responsibility for actually performing the
JDBC can be placed either with the bean itself or with the EJB container.
The term for the former is bean-managed persistence (BMP), and for the latter it is
container-managed persistence (CMP).
Note
The EJB specification is very much oriented around relational data stores.
Certainly, container-managed persistence can only be performed through
JDBC javax.sql.DataSource objects, and JDBC is based around ANSI SQL 92.
If using bean-managed persistence, any API can be used to save the bean’s
state to a persistent data store, but even then, the methods that the bean is
required to provide, such as findByPrimaryKey(), still have a relational
nature to them.
Container-managed persistence was part of the EJB 1.1 specification (the predecessor to
the current EJB 2.0), but attracted much criticism in that release. However, it has been
radically overhauled in EJB 2.0, and now works in a fundamentally different way. This is
so much so that the deployment descriptor even has the cmp-version element to indicate
whether the Entity bean has been written under the 1.1 or 2.0 contract. Tomorrow, you
will learn more about CMP 2.0. For the rest of today, however, you will be focusing on
BMP. That way, you’ll have a pleasant surprise when you realize how much of the coding can be automated using CMP.
Remote Versus Local Interfaces
One of the most significant improvements in the EJB 2.0 specification over previous versions is the inclusion of local interfaces as well as remote interfaces.
All beans that you have seen on previous days have provided only a remote interface.
That is, both their home and remote interfaces have extended from javax.ejb.EJBHome
and javax.ejbEJBObject, both of which, in turn, extend the java.rmi.Remote interface.
6
08 0672323842 CH06
3/20/02
218
9:30 AM
Page 218
Day 6
This ability to invoke methods on a bean without regard for its location is crucial for
Session beans, but for Entity beans it is less useful, even positively harmful. Very often, a
client must deal with many Entity beans to transact some piece of work, and if each of
those Entity beans is remote, this will incur substantial network traffic. There is also the
cost of cloning any serializable objects to enforce the required “pass-by-value” semantics.
Even more frustratingly, the client of the Entity bean may well be a Session bean.
Indeed, it is generally considered bad practice to use anything other than a Session bean
to interact with Entity beans. More often than not, this Session bean will be co-located
(running in the same JVM) as the Entity beans that it is using. The EJB container is
obligated to make all Session-to-Entity bean calls via the network and to clone all serializable objects, just because the Entity beans are remote.
By now, you probably have guessed what local interfaces are. They are alternative nonremote interfaces that the Entity bean can specify. Again, the home and proxy idea is
used, with the home interface being extended from javax.ejb.EJBLocalHome and the
proxy for the bean extending from javax.ejb.EJBLocalObject. Otherwise though, these
are regular Java interfaces, and the normal “pass by reference” semantics for objects
passed across these interfaces apply.
Note
“Pass by reference” is a simpler way of saying “object references are passed
by value.”
An Entity bean can provide a regular home/remote interface, or it can provide a localhome/local interface. Indeed, there is nothing to prevent an Entity bean from offering
both interfaces, although any clients using the remote interface would incur the performance costs already noted. Local interfaces are not specific to Entity beans either;
Session beans can also provide local interfaces. For Session beans (especially stateless
Session beans), there might well be reason to offer both a remote and a local interface. In
general, it would be expected for the two interfaces to offer the same sorts of capabilities,
although there is nothing in the EJB specification that enforces this.
Figure 6.3 shows the two sets of interfaces that a bean can provide.
In both cases, the EJB home/local-home and proxy objects take responsibility for security (Day 15, “Security”) and transactions (Day 8, “Transactions and Persistence”), while
home/remote interfaces also make the physical location of the bean transparent to the
remote client.
Local interfaces are more than just a performance boost for EJBs though, they are the
cornerstone on which container-managed persistence and also container-managed relationships (CMR) are founded. You will learn about these in detail tomorrow.
08 0672323842 CH06
3/20/02
9:30 AM
Page 219
Entity EJBs
219
FIGURE 6.3
EJBs can have local
and remote interfaces.
home
stub
home
remote
stub
remote
remote
stub
security and
transactions
location
transparency
bean
local
home
local
client
local
In the case study and examples for today and tomorrow, you will see that the Entity
beans define only a local interface.
BMP Entity Bean Lifecycle
The lifecycle of both BMP and CMP Entity beans is dictated by the EntityBean interface that the bean must implement. This is shown in Figure 6.4.
FIGURE 6.4
The
interface
java.io.Serializable
javax.ejb.Entity-
interface defines
certain lifecycle methods that must be implemented by Entity
beans.
!
Bean
interface
EnterpriseBean
interface
EntityBean
+setEntityContext(entitycontext:EntityContext):void
+unsetEntityContext():void
+ejbRemove():void
+ejbActivate():void
+ejbPassivate():void
+ejbLoad():void
+ejbStore():void
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 220
220
Day 6
However, although the method names are the same, the obligations of BMP versus CMP
Entity beans for each of those methods are different. This section discusses just those
lifecycle methods for BMP Entity beans. The Job Entity bean from the case study will be
predominantly be used for example code.
To start with, the Entity bean must implement the javax.ejb.EntityBean interface, as
demonstrated with the JobBean class:
package data;
// imports omitted
import javax.ejb.*;
public class JobBean implements EntityBean
{
// implementation omitted
}
The lifecycle as perceived by the Entity bean and as managed by the container is as
shown in Figure 6.5.
FIGURE 6.5
[pool too small]
/setEntityContext
The
[pool too large]
/unsetEntityContext
javax.ejb.EntityBean
lifecycle allows Entity
beans to be pooled.
Pooled
/ejbFindAll
/ejbFindByPrimaryKey
/ejbActivate
/ejbCreate
Creating
exit/^ejbLocalObject.new()
/ejbPostCreate
/ejbPassivate
Cached
/ejbRemove
ejbLoad
/'business method'
ejbStore
The lifecycle is as follows:
• If the EJB container requires an instance of an Entity bean (for example, if the pool
is too small), it will instantiate the bean instance and call its setEntityContext()
method.
08 0672323842 CH06
3/20/02
9:30 AM
Page 221
Entity EJBs
221
• Pooled instances can service finder methods to locate data within the persistent
data store that represents existing beans. More on these finder methods shortly.
• A bean can be associated with an EJBLocalObject proxy (or EJBObject proxy if
the remote interface is in use) in one of two ways.
First, it can be activated by the container via ejbActivate(). The proxy for the
bean exists but has no associated bean. This could occur if the bean had previously
been passivated and a business method has now been invoked on the proxy. It
could also occur if the bean’s proxy was just returned as the result of a finder
method.
Alternatively, the client may be requesting to create an Entity bean via
and then ejbPostCreate(). This usually means that the corresponding data has been inserted into the persistent data store.
ejbCreate()
• When the bean has been associated with its proxy, business methods can be
invoked on it. Before the business method is delegated by the proxy to the bean,
the ejbLoad() lifecycle method will be called, indicating that the bean should reload its state from the persistent data store. Immediately after the business method
has completed, the ejbStore() method is called, indicating that the bean should
update the persistent data store with any change in its state.
• Beans can return to the pooled state in one of two ways.
First, they can be passivated via ejbPassivate(). There is usually little to be done
in the lifecycle, because the bean’s state will already have been saved to the persistent data store during the earlier ejbStore() method. So passivation simply means
that the link from the EJBLocalObject proxy to the bean has been severed.
Alternatively, the client may be requesting to remove the create bean via
This usually means that the corresponding data in the persistent data
store has been deleted.
ejbRemove().
• Finally, if the EJB container wants, it can reduce the size of its pool by first calling
unsetEntityContext().
Note
Most commercial EJB containers provide mechanisms to suppress unnecessary ejbLoad() and ejbStore() calls. None of these mechanisms are in the
EJB specification, however.
Unlike Session beans, there is no binding of the Entity beans to a specific client; the
bean can be shared by all clients.
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 222
222
Day 6
As Figure 6.5 indicated, there are two methods called during the creation of a bean. The
ejbCreate() method is called prior to the EJBLocalObject proxy being made available,
the ejbPostCreate() method is called after the proxy is available. This is shown in the
sequence diagram in Figure 6.6.
FIGURE 6.6
Both the ejbCreate()
and ejbPostCreate()
lifecycle methods are
called when an Entity
bean is created.
localClient
jobHome
jobBean
ctx
JobHomeImpl
JobBean
EntityContext
1: create(ref, customer):JobLocal
1.1: pk:=ejbCreate(ref, customer):JobPk
jobProxy
1.2: proxy:=<constructor>(pk)
JobImpl
1.2.1: set primary key
1.3: set local object
1.4: ejbPostCreate(ref, customer):void
1.4.1:[if required] getEJBLocalObject():EJBLocalObject
Under BMP, the bean has several tasks to perform when its ejbCreate() method is
called. It should:
• Calculate the value of its primary key (if not passed in as an argument).
• Persist itself to a data store. For a RDBMS, this will most likely be in the form of
an SQL INSERT statement or statements.
• Save the supplied arguments and its primary key to fields.
• Return the primary key.
As Figure 6.6 shows, the returned primary key is passed to the bean’s proxy, and the
proxy continues to hold that primary key, even if the bean is subsequently passivated.
The proxy for the bean is also associated with the context object of the bean.
Caution
You can see that the EJBLocalObject proxy holds onto the primary key for
the bean. This allows the bean to be transparently re-loaded if it is passivated. However, because the EJB container is using primary keys for lookups, it
also means the EJB does not allow primary keys to be modified by the application; they must be immutable.
08 0672323842 CH06
3/20/02
9:30 AM
Page 223
Entity EJBs
223
The ejbRemove() method is the opposite of the ejbCreate() method; it removes a
bean’s data from the persistent data store. The implementation of ejbCreate() and
ejbRemove() is given in the “Implementing javax.ejb.EntityBean” section later today.
That takes care of creating and removing beans, but what about when a bean is queried
or updated? The most significant of the Entity bean lifecycle methods are the ejbLoad()
and ejbStore()methods. Together, these methods ensure that the Entity bean is kept in
sync with the persistent data store. The ejbLoad() method is called immediately prior to
any business method (so that a query access the most up-to-date data). The ejbStore()
is called after the business method completes (so that if the method updated the bean’s
state, this is reflected in the persistent data store). Figure 6.7 shows this as a UML
sequence diagram.
FIGURE 6.7
EjbContainer
The ejbLoad() and
ejbStore() methods
keep the bean in sync
with the persistent data
store.
localClient
jobProxy
jobBean
JobImpl
JobBean
DatabaseDriver
1: businessMethod
1.1:[if passivated] ejbActivate():void
1.2: ejbLoad():void
1.2.1: select … where PK = …
1.3: businessMethod
1.4: ejbStore():void
1.4.1: update … where PK = …
2:[if required] ejbPassivate():void
Again, the actual implementation for these methods is given in the “Implementing
javax.ejb.EntityBean” section later today.
As you will recall, Session beans have ejbActivate() and ejbPassivate() methods,
and so do Entity beans. If the EJB container wants to reduce the number of bean
instances, it can passivate the bean. This is only ever done after an ejbStore(), so the
data represented by the bean is not lost. Also, the proxy for the bean continues to hold
the bean’s primary key, meaning that if the client interacts with the bean (through the
proxy) in the future, the appropriate data can be loaded from the persistent data store.
Generally, then, there is little or nothing to be done when an Entity bean is passivated or
activated.
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 224
224
Day 6
These lifecycle methods allow new beans (and data in the persistent data store) to be created or removed and updating existing beans, but what about actually finding beans that
already exist? In other words, in JDBC terms, you have seen the lifecycle methods that
correspond to SQL INSERT, DELETE, and UPDATE statements, but what of an SQL SELECT
statement? Well, this is accomplished by the finder methods. The EJB specification
requires at least one finder method, whose name must be ejbFindByPrimaryKey(), and
allows other finder methods, whose names must begin ejbFind. These methods have corresponding methods in the local-home interface, so you’ll be learning about them shortly
as part of specifying and implementing the bean.
One obvious question arises, “When the client invokes the finder method on the home
interface, which bean actually performs the ejbFindXxx() method?” The answer is perhaps
a little unexpected; any unused (that is, pooled) bean will be used by the EJB container.
Learning all these lifecycle methods for both Entity and Session beans can be somewhat
overwhelming at first, made all the more complicated because some method names
appear for both bean types but imply different responsibilities. To clarify matters, Table
6.1 compares the two sets of lifecycle methods and identifies those responsibilities.
TABLE 6.1
Responsibilities of Session and Entity Beans Sit in Different Lifecycle Methods
Lifecycle Method
Session Bean
Entity Bean
setXxxContext()
Set context
Set context
unsetXxxContext()
N/A
Unset context
ejbFindByPrimaryKey()
N/A
Acquire reference to proxy
ejbCreate()
Acquire reference to proxy
a) Insert data to persistent data store
b) Acquire reference to proxy
ejbPostCreate()
N/A
Access proxy if necessary
ejbActivate()
a) Loaded from (temporary)
data store
b) Obtain environmental
resources
Obtain environmental resources
ejbPassivate()
a) Saved to (temporary)
data store;
b) Release environmental
resources
Release environmental resources
ejbLoad()
N/A
Load from (persistent) data store
ejbStore()
N/A
Save to (persistent) data store
ejbRemove()
Release reference to proxy
a) Delete data from persistent data
store
b) Release reference to proxy
08 0672323842 CH06
3/20/02
9:30 AM
Page 225
Entity EJBs
225
Specifying a BMP Entity Bean
Following the pattern of Session beans, specifying an Entity bean involves defining the
local-home and the local interface:
• The local-home interface extends javax.ejb.EJBLocalHome.
• The local interface extends javax.ejb.EJBLocalObject.
A discussion on each of these interfaces follows.
Local-Home Interface
Listing 6.1 shows the complete JobLocalHome interface as an example.
LISTING 6.1
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
JobLocalHome Interface
package data;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
public interface JobLocalHome extends EJBLocalHome
{
JobLocal create (String ref, String customer) throws CreateException;
JobLocal findByPrimaryKey(JobPK key) throws FinderException;
Collection findByCustomer(String customer) throws FinderException;
Collection findByLocation(String location) throws FinderException;
void deleteByCustomer(String customer);
}
Each of these methods has a corresponding method in the bean class itself. Taking the
JobBean code as an example:
• The create(String
method in JobBean corresponds to
customer) in the JobLocalHome interface.
ref, String customer)
ejbCreate(String ref, String
• The ejbFindByPrimaryKey(String name) method in JobBean corresponds to
findByPrimaryKey(String name) in the JobLocalHome interface.
• The ejbFindByCustomer(String
method in JobBean corresponds to
in the JobLocalHome interface.
customer)
findbyCustomer(String customer)
• The ejbHomeDeleteByCustomer(String customer) in JobBean corresponds to
deleteByCustomer(String customer) in the JobLocalHome interface.
6
08 0672323842 CH06
3/20/02
9:30 AM
Page 226
226
Day 6
Note
Note that for home methods discussed shortly, the convention is to append
ejbHome, not just ejb, to the bean’s method name.
This seems straight-forward enough, but note that the return types for the bean’s
ejbCreate() and ejbFindXxx() methods are different from the return types of the methods in the local-home interface. Specifically, while the bean returns (to the EJB container) either primary key objects or Collections of primary key objects, the local-home
interface methods return (to the client) either local proxies (that is, instances of objects
that implement the JobLocal interface, for the example) or Collections of such.
Create and Remove Methods
The list of exceptions thrown by the local-home methods and the bean’s corresponding
methods should match in each case. For the createXXX() method, the list should be the
union of the exceptions thrown by both ejbCreateXXX() and ejbPostCreateXXX(). If a
home and a remote interface are being provided for the Entity bean, the
java.rmi.RemoteException must be declared for the methods of the home interface.
As well as the create() method, the local-home interface inherits a remove(Object o)
method from javax.ejb.EJBLocalHome. This corresponds to the ejbRemove() lifecycle
method of the bean itself.
Finder Methods
Finder methods in the bean return either a single primary key (if a single bean matches
the underlying query) or a java.util.Collection of primary keys (if there is more than
one matching bean). The ejbFindByPrimaryKey() method is always required to be one
of the bean’s methods, although it is not part of the EntityBean interface. This is
because the argument type and return type will depend upon the bean.
Note
It is also possible for finder methods to return java.util.Enumerations. This
dates from EJB 1.0 before the Java Collections API was introduced in J2SE
1.2 and should not be used.
Obviously, to specify the findByPrimaryKey() method, the primary key of the Entity
bean must have been identified. As was noted earlier today, if persisting to an RDBMS,
identifying the primary key is probably quite easy, because the primary key will correspond to the columns of the primary key of the underlying RDBMS table. A customdeveloped primary key class is needed when two or more fields identify the bean; otherwise, the type of the single field of the bean that represents the key is used.
08 0672323842 CH06
3/20/02
9:31 AM
Page 227
Entity EJBs
Note
227
If a single field of the bean is used as the primary key, that field must not be
a primitive type (such as an int or long). Primary key fields must be actual
classes, such as a java.lang.String. Furthermore, the EJB specification does
not allow primary keys to change once assigned, so it is best if the class chosen is immutable.
Custom Primary Key Classes
As noted earlier, the primary key can be either a field of the bean (in which case, the primary key class is just the class of that field) or can be a custom-developed class. The latter is required if more than one field is needed to identify the bean (and can be used even
for single field keys).
For the JobBean, the primary key is a combination of the customer and the job reference
(the customer and ref fields, respectively). Because the primary key is composite, a custom primary key class is needed; this is the JobPK class.
Custom primary key classes are required to follow a number of rules. Specifically
• The class must implement java.io.Serializable or java.io.Externalizable.
• The values of the class must all be primitives or be references to objects that, in
turn, are serializable.
• The equals() method and the hashCode() methods must be implemented.
• There must be a no-arg constructor (there can also be other constructors that take
arguments, but they would only be provided for convenience).
In other words, the class must be what is sometimes referred to as a value type.
Note
At least conceptually, value types are immutable (there should be no setter
methods; they cannot be changed). The requirement for a no-arg constructor does prevent this from actually being the case.
Listing 6.2 shows the JobPK primary key class.
LISTING 6.2
JobPK Class Identifies a Job
1: package data;
2:
3: import java.io.*;
4: import javax.ejb.*;
5:
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 228
228
Day 6
LISTING 6.2
Continued
6: public class JobPK implements Serializable {
7:
public String ref;
8:
public String customer;
9:
10:
public JobPK() {
11:
}
12:
public JobPK(String ref, String customer) {
13:
this.ref = ref;
14:
this.customer = customer;
15:
}
16:
17:
public String getRef() {
18:
return ref;
19:
}
20:
public String getCustomer() {
21:
return customer;
22:
}
23:
24:
public boolean equals(Object obj) {
25:
if (obj instanceof JobPK) {
26:
JobPK pk = (JobPK)obj;
27:
return (pk.ref.equals(ref) && pk.customer.equals(customer));
28:
}
29:
return false;
30:
}
31:
public int hashCode() {
32:
return (ref.hashCode() ^ customer.hashCode());
33:
}
34:
public String toString() {
35:
return “JobPK: ref=\”” + ref + “\”, customer=\”” +
➥ customer + “\””;
36:
37: }
}
Note that the ref and customer fields have public visibility. This is a requirement of the
EJB specification. Each field must correspond—in name and type—to one of the fields
of the bean itself. This might seem like a strange requirement, but is needed by the EJB
container to manage CMP beans.
To implement the equals() method, test that all fields of the object have the same value
as the fields in the provided object. For primitive values, the regular == operator should
be used, but for object references, the equals() method must be called.
To implement the hashCode() method, generate an int value that is based entirely and
deterministically on the value of the fields, such that
if A.equals(B) then A.hashCode()
== B.hashCode().
08 0672323842 CH06
3/20/02
9:31 AM
Page 229
Entity EJBs
229
There are a couple of ways to accomplish this. A quick way to do this is to convert all
the values of the primary key class’ fields to Strings, concatenate them to a single
String, and then invoke the hashCode() on this resultant string. Alternatively, the
hashCode() values for all of the fields could be or’d together using the ^ operator. At
runtime, this will execute more quickly than the concatenation approach, but it does
mean that the distribution of hashcodes may be less good for primary keys with many
fields. This is the approach used in Listing 6.2.
Tip
Creating these primary key classes can be somewhat tedious. But remember
that if there is a single (non-primitive) field in the bean that identifies that
bean, this can be used instead.
Failing that, a single primary key class can be used for multiple beans. For
example, you could create a IntPK class that just encapsulates an int primitive value.
Home Methods
In addition to finder, create, and remove methods, it is also possible to define home
methods within the local-home interface. These are arbitrary methods that are expected
to perform some business-type functionality related to the set of beans. In other words,
they are an EJB equivalent of Java class methods (defined with the static keyword).
Some common uses for home methods include defining a batch operation to be performed on all bean instances (such as decreasing the price of all catalogue items for a
sale), or various utility methods, such as formatting a bean’s state for a toString()
method.
Caution
One question that sometimes arises is whether all database updates should
be performed through Entity bean methods. One example given for a home
method of a bean would be to decrease the price of all catalogue items for
a sale. Iterating over perhaps 10,000 catalogue items and invoking
setPrice( getPrice() * 0.9 ) is clearly going to cause massive amounts of
SQL hitting the back-end RDBMS (or equivalent persistent data store).
In J2SE programs, a simple update, such as
UPDATE catalogue
set price = price * 0.9
is clearly the way to go. The ejbLoad() lifecycle method will ensure that any
catalog item Entity bean will re-sync its state with the RDBMS.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 230
230
Day 6
Local Interface
Just as for Session beans with their remote interfaces, the local interface defines the
capabilities of the Entity bean. Because first and foremost an Entity bean represents data,
it is entirely to be expected that many of the methods exposed through the local interface
will be simple getter and setter methods. Listing 6.3 shows the local interface for the Job
bean.
LISTING 6.3
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
JobLocal Interface
package data;
import java.rmi.*;
import javax.ejb.*;
public interface JobLocal extends EJBLocalObject
{
String getRef();
String getCustomer();
CustomerLocal getCustomerObj(); // derived
void setDescription(String description);
String getDescription();
void setLocation(LocationLocal location);
LocationLocal getLocation();
Collection getSkills();
void setSkills(Collection skills);
}
Note that the setLocation() method accepts a LocationLocal reference rather than,
say, a String containing the name of a location. In other words, the Job bean is defining
its relationships to other beans, in this case the Location bean directly, effectively
enforcing referential integrity. The client of the Job Entity bean is thus required to supply
a valid location or none at all.
This is not to say that Entity beans cannot provide further processing. An example often
quoted might be for a SavingsAccountBean. This might provide withdraw() and
deposit() methods. The withdraw() method might well ensure that the balance can
never go below zero. The bean might also provide an applyInterest() method, but it
almost certainly would not provide a setBalance() method (if only!).
Each of these methods has a corresponding method in the bean itself, and the exceptions
list matches exactly. The implementation is shown in the “Implementing the LocalInterface Methods” section later today.
08 0672323842 CH06
3/20/02
9:31 AM
Page 231
Entity EJBs
Note
231
Actually, such an SavingsAccountBean might well provide a setBalance()
method, but would restrict access to administrators. You will learn more
about security on Day 15, “Security.”
Implementing a BMP Entity Bean
Implementing an Entity bean involves providing an implementation for the methods of
the javax.ejb.EntityBean, corresponding methods for each method in the local-home
interface, and a method for each method in the local interface.
Implementing javax.ejb.EntityBean
The setEntityContext() method is a good place to perform JNDI lookups, for example
to acquire a JDBC DataSource reference. Listing 6.4 shows how this is done for the
JobBean code.
LISTING 6.4
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:
JobBean.setEntityContext() Method
package data;
import javax.ejb.*;
import javax.naming.*;
import javax.sql.*;
// imports omitted
public class JobBean implements EntityBean
{
public void setEntityContext(EntityContext ctx) {
this.ctx = ctx;
InitialContext ic = null;
try {
ic = new InitialContext();
dataSource = (DataSource)
➥ic.lookup(“java:comp/env/jdbc/Agency”);
skillHome = (SkillLocalHome)
➥ic.lookup(“java:comp/env/ejb/SkillLocal”);
locationHome = (LocationLocalHome)
➥ic.lookup(“java:comp/env/ejb/LocationLocal”);
customerHome = (CustomerLocalHome)
➥ic.lookup(“java:comp/env/ejb/CustomerLocal”);
}
catch (NamingException ex) {
error(“Error looking up depended EJB or resource”,ex);
return;
}
}
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 232
232
Day 6
LISTING 6.4
Continued
26:
27:
28:
29:
30: }
private Context ctx;
private DataSource dataSource
// code omitted
The unsetEntityContext() method (not shown) usually just sets these fields to null.
The ejbLoad() and ejbStore() methods are responsible for synchronizing the bean’s
state with the persistent data store. Listing 6.5 shows these methods for JobBean.
LISTING 6.5
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:
JobBean’s ejbLoad() and ejbStore() Methods
package data;
import javax.ejb.*;
import java.sql.*;
// imports omitted
public class JobBean implements EntityBean
{
public void ejbLoad(){
JobPK key = (JobPK)ctx.getPrimaryKey();
Connection con = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”SELECT description,location
➥ FROM Job
➥ WHERE ref = ? AND customer = ?”);
stmt.setString(1, key.getRef());
stmt.setString(2, key.getCustomer());
rs = stmt.executeQuery();
if (!rs.next()) {
error(“No data found in ejbLoad for “ + key, null);
}
this.ref = key.getRef();
this.customer = key.getCustomer();
this.customerObj =
➥customerHome.findByPrimaryKey(this.customer); // derived
this.description = rs.getString(1);
String locationName = rs.getString(2);
this.location = (locationName != null) ?
➥locationHome.findByPrimaryKey(locationName) : null;
// load skills
08 0672323842 CH06
3/20/02
9:31 AM
Page 233
Entity EJBs
LISTING 6.5
Continued
30:
stmt = con.prepareStatement(
➥”SELECT job, customer, skill
➥ FROM JobSkill
➥ WHERE job = ? AND customer = ?
➥ ORDER BY skill”);
stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
rs = stmt.executeQuery();
List skillNameList = new ArrayList();
while (rs.next()) {
skillNameList.add(rs.getString(3));
}
this.skills = skillHome.lookup(skillNameList);
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:
233
}
catch (SQLException e) {
error(“Error in ejbLoad for “ + key, e);
}
catch (FinderException e) {
error(“Error in ejbLoad (invalid customer or location) for “
➥+ key, e);
}
finally {
closeConnection(con, stmt, rs);
}
}
public void ejbStore(){
Connection con = null;
PreparedStatement stmt = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”UPDATE Job
➥ SET description = ?, location = ?
➥ WHERE ref = ? AND customer = ?”);
stmt.setString(1, description);
if (location != null) {
stmt.setString(2, location.getName());
} else {
stmt.setNull(2, java.sql.Types.VARCHAR);
}
stmt.setString(3, ref);
stmt.setString(4, customerObj.getLogin());
stmt.executeUpdate();
// delete all skills
stmt = con.prepareStatement(
➥”DELETE FROM JobSkill
➥ WHERE job = ? and customer = ?”);
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 234
234
Day 6
LISTING 6.5
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89: }
Continued
stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.executeUpdate();
// add back in all skills
for (Iterator iter = getSkills().iterator(); iter.hasNext();){
SkillLocal skill = (SkillLocal)iter.next();
stmt = con.prepareStatement(
➥”INSERT INTO JobSkill (job,customer,skill)
➥ VALUES (?,?,?)”);
stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.setString(3, skill.getName());
stmt.executeUpdate();
}
}
catch (SQLException e) {
error(“Error in ejbStore for “ + ref + “,” + customer, e);
}
finally {
closeConnection(con, stmt, null);
}
}
// code omitted
In the ejbLoad() method, the JobBean must load its state from both the Job and
JobSkill tables, using the data in the JobSkill table to populate the skills field. In the
ejbStore() method, the equivalent updates to the Job and JobSkill tables occur.
Of course, there is the chance that when the bean comes to save itself, the data could have
been removed. This would happen if some user manually deleted the data; there is nothing
in the EJB specification to require that an Entity bean “locks” the underlying data. In such
a case, the bean should throw a javax.ejb.NoSuchEntityException; in turn, this will be
returned to the client as some type of java.rmi.RemoteException. This was mentioned
briefly yesterday, so look back to refresh your memory if needed. And remember, you will
be learning more about exception handling and transactions on Day 8.
Note
To keep the case study as small and understandable as possible, the error
handling in JobBean is slightly simplified. In Listing 6.5, the code will throw
an EJBException (rather than NoSuchEntityException) from
ejbLoad() if the data has been removed. In ejbStore(), it doesn’t actually check to see if any rows were updated, so no exception would be thrown.
08 0672323842 CH06
3/20/02
9:31 AM
Page 235
Entity EJBs
235
More complex beans can perform other processing within the ejbLoad() and
ejbStore() methods. For example, the data might be stored in some denormalized form
in a relational database, perhaps for performance reasons. The ejbStore() method
would store the data in this de-normalized form, while the ejbLoad() methods would
effectively be able to re-normalize the data on-the-fly. The client need not be aware of
these persistence issues.
Another idea: these methods could be used to handle text more effectively. The EJB
specification suggests compressing and decompressing text, but they could also perhaps
do searches for keywords within the text, and then redundantly store these keywords separately, or the data might be converted into XML format.
As noted earlier today, there is usually very little or nothing to be done when an Entity
bean is passivated or activated. Listing 6.6 shows this.
LISTING 6.6
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
JobBean’s ejbActivate() and ejbPassivate() Methods
package data;
import javax.ejb.*;
// imports omitted
public class JobBean implements EntityBean
{
public void ejbPassivate(){
ref = null;
customer = null;
customerObj = null;
description = null;
location = null;
}
public void ejbActivate(){
}
// code omitted
}
Implementing the Local-Home Interface Methods
The implementation of ejbCreate() and ejbPostCreate() for the JobBean is shown in
Listing 6.7.
LISTING 6.7
JobBean’s ejbCreate() and ejbPostCreate() Methods
1: package data;
2:
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 236
236
Day 6
LISTING 6.7
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:
Continued
import javax.ejb.*;
import javax.sql.*;
// imports omitted
public class JobBean implements EntityBean
{
private String ref;
private String customer;
private String description;
private LocationLocal location;
private CustomerLocal customerObj; // derived
private List skills;
// vector field; list of SkillLocal ref’s.
public String ejbCreate (String ref, String customer)
➥throws CreateException {
// validate customer login is valid.
try {
customerObj = customerHome.findByPrimaryKey(customer);
} catch (FinderException ex) {
error(“Invalid customer.”, ex);
}
JobPK key = new JobPK(ref, customer);
try {
ejbFindByPrimaryKey(key);
throw new CreateException(“Duplicate job name: “ + key);
}
catch (FinderException ex) { }
Connection con = null;
PreparedStatement stmt = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”INSERT INTO Job (ref,customer)
➥ VALUES (?,?)”);
stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.executeUpdate();
}
catch (SQLException e) {
error(“Error creating job “ + key, e);
}
finally {
closeConnection(con, stmt, null);
}
this.ref = ref;
this.customer = customer;
this.description = description;
this.location = null;
this.skills = new ArrayList();
08 0672323842 CH06
3/20/02
9:31 AM
Page 237
Entity EJBs
LISTING 6.7
49:
50:
51:
52:
53: }
237
Continued
return key;
}
public void ejbPostCreate (String name, String description) {}
This particular implementation validates that the customer exists (jobs are identified by
customer and by a unique reference), and it also makes sure that the full primary key
does not already exist in the database. If it does, the BMP bean throws a
CreateException. If it doesn’t (represented by the ejbFindByPrimaryKey() call throwing a FinderException), the method continues.
An alternative implementation would have been to place a unique index on the Job table
within the RDBMS and then to catch the SQLException that might be thrown if a duplicate is attempted to be inserted.
Caution
There is a race condition here. It’s possible that another user could insert a
record between the check for duplicates and the actual SQL INSERT. The
ejbCreate() method is called within a transaction; changing the RDBMS
isolation level (in a manner specified by the EJB container) would eliminate
this risk, although deadlocks could then occur.
Note that the skills field is set to an empty ArrayList. This holds a list of SkillLocal
references, this being the local interface to the Skill bean. Of course, for a newly created Job bean, this list is empty. The decision for the skills field to hold references to
SkillLocal objects rather than, say, just Strings holding the skill names, was taken
advisedly. If the skill name is used (that is, the primary key of a skill), finding information about the skill would require extra steps. Perhaps more compellingly, this is also the
approach taken for CMP beans and container-managed relationships, discussed in detail
tomorrow.
Also noteworthy is the customerObj field. The Job, when created, is passed just a
String containing the customer’s name. In other words, this is a primary key to a customer. The customerObj field contains a reference to the parent customer bean itself by
way of its CustomerLocal reference.
Both the skills and the customerObj fields illustrate (for want of a better phrase) beanmanaged relationships. For the skills field, this is a many-to-many relationship, from
Job to Skill. For the customerObj field, this is a many-to-one relationship from Job to
Customer.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 238
238
Day 6
As for the stateful Session beans that you learned about yesterday, the ejbCreate() and
ejbPostCreate() methods both correspond to a single method called create() in the
bean’s local-home interface. The list of arguments must correspond. Again, as for
Session beans, it is possible for there to be more than one create method with different
sets of arguments, or indeed the createXXX() method naming convention can be used
instead of overloading the method name of create().
The ejbRemove() method is the opposite of the ejbCreate() method; it removes a
bean’s data from the persistent data store. Its implementation for JobBean is shown in
Listing 6.8.
LISTING 6.8
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:
JobBean’s ejbRemove() Method
package data;
import javax.ejb.*;
import javax.naming.*;
// imports omitted
public class JobBean implements EntityBean
{
public void ejbRemove(){
JobPK key = (JobPK)ctx.getPrimaryKey();
Connection con = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
con = dataSource.getConnection();
stmt1 = con.prepareStatement(
➥”DELETE FROM JobSkill
➥ WHERE job = ? and customer = ?”);
stmt1.setString(1, ref);
stmt1.setString(2, customerObj.getLogin());
stmt2 = con.prepareStatement(
➥”DELETE FROM Job
➥ WHERE ref = ? and customer = ?”);
stmt2.setString(1, ref);
stmt2.setString(2, customerObj.getLogin());
stmt1.executeUpdate();
stmt2.executeUpdate();
}
catch (SQLException e) {
error(“Error removing job “ + key, e);
}
finally {
closeConnection(con, stmt1, null);
closeConnection(con, stmt2, null);
}
ref = null;
08 0672323842 CH06
3/20/02
9:31 AM
Page 239
Entity EJBs
LISTING 6.8
239
Continued
33:
34:
35:
36:
37:
38:
39: }
customer = null;
customerObj = null;
description = null;
location = null;
}
// code omitted
Each of the finder methods of the local-home interface must have a corresponding
method in the bean. By way of example, Listing 6.9 shows two of the (three) finder
methods for the JobBean.
LISTING 6.9
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:
JobBean’s Finder Methods
package data;
import javax.ejb.*;
import java.sql.*;
import java.util.*;
// imports omitted
public class JobBean implements EntityBean
{
public JobPK ejbFindByPrimaryKey(JobPK key) throws FinderException {
Connection con = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”SELECT ref
➥ FROM Job
➥ WHERE ref = ? AND customer = ?”);
stmt.setString(1, key.getRef());
stmt.setString(2, key.getCustomer());
rs = stmt.executeQuery();
if (!rs.next()) {
throw new FinderException(“Unknown job: “ + key);
}
return key;
}
catch (SQLException e) {
error(“Error in findByPrimaryKey for “ + key, e);
}
finally {
closeConnection(con, stmt, rs);
}
return null;
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 240
240
Day 6
LISTING 6.9
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: }
Continued
}
public Collection ejbFindByCustomer(String customer)
➥throws FinderException {
Connection con = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”SELECT ref, customer
➥ FROM Job
➥ WHERE customer = ?
➥ ORDER BY ref”);
stmt.setString(1, customer);
rs = stmt.executeQuery();
Collection col = new ArrayList();
while (rs.next()) {
String nextRef = rs.getString(1);
String nextCustomer = rs.getString(2);
// validate customer exists
CustomerLocal nextCustomerObj =
➥customerHome.findByPrimaryKey(nextCustomer);
col.add(new JobPK(nextRef, nextCustomerObj.getLogin()));
}
return col;
}
catch (SQLException e) {
error(“Error in findByCustomer: “ + customer, e);
}
catch (FinderException e) {
error(“Error in findByCustomer, invalid customer: “ +
➥customer, e);
}
finally {
closeConnection(con, stmt, rs);
}
return null;
}
// code omitted
The implementation of the ejbFindByPrimaryKey() method might seem somewhat
unusual; it receives a primary key, and then returns it. Of course, what it has done as well
is to have validated that an entity exists for the given primary key; if there were none, a
javax.ejb.ObjectNotFoundException would be thrown. The implementation of
ejbFindByCustomer() is straightforward enough.
08 0672323842 CH06
3/20/02
9:31 AM
Page 241
Entity EJBs
241
The Job bean defines a home method, namely deleteByCustomer(), and the
corresponding method in the JobBean class is ejbHomeDeleteByCustomer(), as shown
in Listing 6.10.
LISTING 6.10
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:
JobBean.ejbHomeDeleteByCustomer() Home Method
package data;
import javax.ejb.*;
import java.sql.*;
import java.util.*;
// imports omitted
public class JobBean implements EntityBean
{
public void ejbHomeDeleteByCustomer(String customer) {
Connection con = null;
PreparedStatement stmt2 = null;
PreparedStatement stmt1 = null;
try {
con = dataSource.getConnection();
stmt1 = con.prepareStatement(
➥”DELETE FROM JobSkill
➥ WHERE customer = ?”);
stmt2 = con.prepareStatement(
➥”DELETE FROM Job
➥ WHERE customer = ?”);
stmt1.setString(1, customer);
stmt2.setString(1, customer);
stmt1.executeUpdate();
stmt2.executeUpdate();
}
catch (SQLException e) {
error(“Error removing all jobs for “ + customer, e);
}
finally {
closeConnection(con, stmt1, null);
closeConnection(con, stmt2, null);
}
}
// code omitted
}
Implementing the Local Interface Methods
Each of the methods in the local interface has a corresponding method in the bean itself.
The corresponding methods for JobBean are shown in Listing 6.11.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 242
242
Day 6
LISTING 6.11
Business Methods of JobBean Correspond to the Methods of the Local
Interface
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:
package data;
import java.rmi.*;
import javax.ejb.*;
// imports omitted
public class JobBean implements EntityBean
{
public String getRef() {
return ref;
}
public String getCustomer() {
return customer;
}
public CustomerLocal getCustomerObj() {
return customerObj;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocationLocal getLocation() {
return location;
}
public void setLocation(LocationLocal location) {
this.location = location;
}
/** returns (copy of) skills */
public Collection getSkills() {
return new ArrayList(skills);
}
public void setSkills(Collection skills) {
// just validate that the collection holds references to
➥SkillLocal’s
for (Iterator iter = getSkills().iterator(); iter.hasNext(); ) {
SkillLocal skill = (SkillLocal)iter.next();
}
// replace the list of skills with that defined.
this.skills = new ArrayList(skills);
}
// code omitted
}
The getSkills() and setSkills() methods bear closer inspection. The getSkills()
method returns a copy of the local skills field because, otherwise, the client could
08 0672323842 CH06
3/20/02
9:31 AM
Page 243
Entity EJBs
243
change the contents of the skills field without the JobBean knowing. This isn’t an issue
that would have arisen if the interface to JobBean was remote, because a copy would
automatically have been created. Turning to the setSkills() method, this checks to
make sure that the new collection of skills supplied is a list of SkillLocal references.
This is analogous to the setLocation() method that was discussed; the Job Entity bean
is enforcing referential integrity with the Skill Entity bean.
Generating IDs
Sometimes an Entity bean already has a field (or fields) that represent the primary key,
but at other times, the set of fields required may just be too large. Alternatively, the obvious primary key may not be stable in the sense that its value could change over the lifetime of an entity—something prohibited by the EJB specification. For example, choosing
a (lastname, firstname) as a means of identifying an employee may fail if a female
employee gets married and chooses to adopt her husband’s surname.
In these cases, it is common to introduce an artificial key, sometimes known as a surrogate key. You will be familiar with these if you have ever been allocated a customer number when shopping online. Your social security number, library card number, driver’s
license, and so on may well be just a pretty meaningless jumble of numbers and letters,
but they are guaranteed to be unique and stable. These are all surrogate keys.
With BMP Entity beans, the responsibility for generating these ID values is yours, the
bean provider. Whether numbers and letters or just numbers are used is up to you,
although just numbers are often used in an ascending sequence (that is, 1, 2, 3, and so
on). If you adopt this strategy, you could calculate the values by calling a stateless
Session bean—a number fountain, if you will. A home method could perhaps encapsulate the lookup of this NumberFountainBean.
The implementation of such a NumberFountainBean can take many forms. There will
need to be some persistent record of the maximum allocated number in the series, so a
method such as getNextNumber(“MyBean”) could return a value by performing an appropriate piece of SQL against a table held in an RDBMS:
begin tran
update number_fountain
set
max_value = max_value + 1
where bean_name = “MyBean”
select max_value
from
number_fountain
where bean_name = “MyBean”
commit
6
08 0672323842 CH06
3/20/02
244
9:31 AM
Page 244
Day 6
One disadvantage with this approach is that the NumberFountainBean—or rather, the
underlying database table—can become a bottleneck. A number of strategies have been
developed to reduce this. One is to make the getNextNumber() method occur in a different transaction from the rest of the work. You will learn more about transactions on Day
8; for now, it is just necessary to know that while this will increase throughput, there is
the risk of gaps occurring in the sequence.
If non-contiguous sequences are acceptable, even better throughput can be achieved by
implementing a stateless Session bean that caches values in memory. Thus, rather than
incrementing the maximum value by 1, it can increment by a larger number, perhaps by
100. Only every 100th call actually performs an SQL update, and the other 99 times, the
number is allocated from memory. Of course, if the system crashes or power fails, there
could be quite a large gap.
A final enhancement that improves scalability further and also improves resilience is to
arrange for there to be a number of beans, each with a range of values. For example,
these might be allocated using a high-order byte/low-order bytes arrangement.
Note
Countries that allocate car license plates by state or by district are effectively
using this approach.
One advantage of implementing a Session bean, such as NumberFountainBean, is that it
isolates the dependency on the persistent data store that is holding the maximum value.
Also, the SQL to determine the next available number is easily ported across RDBMS.
On the other hand, many organizations use only a single RDBMS, so such portability is
not needed. In these cases, the RDBMS may have built-in support for allocating monotonically increasing numeric values, and this can be used directly. For example, SEQUENCEs
can be used in Oracle, while both Microsoft SQL Server and Sybase provide so-called
identity columns and the @@identity global variable. So, for BMP Entity beans, another
way to obtain the next value is to perform the SQL INSERT, obtaining the value from the
back-end RDBMS. Note that most of these tools have the same scalability issues as the
home-grown NumberFountainBean, and most also provide optimizations that can result
in gaps in the series.
There is an alternative to using numbers for ID values, namely to generate a value that
must be unique. On Windows PCs, you may well have seen strings in the format
{32F8CA14-087C-4908-B7C4-6757FE7E90AB}
In case you are wondering, this was found by delving into the Windows Registry and
(apparently) represents the FormatGUID for .AVI files (whatever that means!). The point
08 0672323842 CH06
3/20/02
9:31 AM
Page 245
Entity EJBs
245
is that it is—to all intents and purposes—guaranteed to be unique. In the case of GUIDs,
it is unique because it is based on the MAC address of the ethernet card of the PC, plus
the time.
Clearly, other algorithms can be created, and a quick search on the Web should throw up
some commercial products and free software from which to select. For example, one
algorithm generates values unique to the millisecond, the machine, the object creating the
ID, and the top-most method of the call stack.
Granularity Revisited
A recurrent theme when developing Entity beans is in selecting an appropriate granularity for the bean. Prior to EJB 2.0, Entity beans could only provide a remote interface,
which meant that a relatively coarse grained interface was required to minimize the
client-to-bean network traffic. Indeed, this is still the recommendation made for Session
beans in EJB 2.0 that have a remote interface.
With EJB 2.0, Entity beans can have a local interface, meaning that the cost of interaction with the client becomes minimal. If the cost of interaction of the Entity bean to the
persistent data store is not too high, fine-grained Entity beans are quite possible. This
may be true, either because the EJB container can optimize the database access in some
way (true only for CMP Entity beans) or if the data store resides on the same computer
as the EJB container and, ideally, within the same JVM process space.
Note
Running a persistent data store in the same process space as the EJB container is quite possible; a number of pure Java RDBMS—including Cloudscape,
the database bundled with the J2EE RI—provide an “embedded mode.”
Under BMP however, the advice is generally not to use fine-grained Entity beans, principally because the EJB container will be unable to perform any database access optimization. Choosing the granularity is then best determined by focusing on the identity and
lifecycle on the candidate Entity beans. Hence, order and order-detail should be a single
Order bean, but customer and order, while related, should be kept separate.
In the case study, you will find that the Job bean writes to both the Job table and also the
JobSkill table (to record the skill(s) needed to perform the job).
Beware Those Finder Methods!
As you now have learned, Entity beans can be created, removed, and found through their
home interface. While these all seem straightforward enough operations, there’s danger
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 246
246
Day 6
lurking in the last of these; finder methods can cripple the performance of your application if used incorrectly.
This probably doesn’t seem obvious, but if you consider the interplay between the EJB
container (implementing the local-home interface) and the bean itself, it becomes easier
to see:
• The local-home interface’s findManyByXxx() method is called. For the purposes
of this discussion, this finder method returns a Collection.
• The local-home interface delegates to a pooled bean, calling its
ejbFindManyByXxx() method. This performs an SQL SELECT statement (or the
equivalent), and returns back a Collection of primary keys to the local-home
interface.
• The local-home interface instantiates a Collection the same size as was obtained
from the bean and populates it with local proxies. Each local proxy is assigned a
primary key.
So far so good, the client receives a Collection of proxies. Suppose now that the client
iterates over this collection, calling some getter method getXxx().
• The client calls getXxx() on the first proxy in its Collection. The proxy holds a
primary key, but there is no corresponding bean actually associated with the proxy.
Therefore, the EJB container activates a bean from the pool, calls its ejbLoad()
lifecycle method, and then finally delegates the getXxx() business method. After
that method has completed, the ejbStore() method is called.
• This process continues for all of the proxies in the collection.
You can probably see the problem; the persistent data store will be hit numerous times.
First, it will be hit as a result of the ejbFindManyByXxx() method; this will return a thin
column of primary key values. Then, because ejbLoad() is called for each bean, the rest
of the data for that row is returned. This is shown in Figure 6.8.
Consequently, if 20 primary keys were returned by the bean following the initial
the network would be transversed 21 times, and the database will
be hit in all 41 times—once for the initial SELECT and two times each for each of the
beans.
ejbFindManyByXxx(),
There are a number of techniques that can eliminate this overhead, each with pros and
cons:
• The most obvious solution is to not use finder methods that return Collections of
many beans. Instead, use stateless Session beans that perform a direct SQL SELECT
query against the database, iterate over the ResultSet, and return back a
Collection of serializable value objects that mirror the data contained in the actual
entity. This technique is called the Fast-lane Reader.
08 0672323842 CH06
3/20/02
9:31 AM
Page 247
Entity EJBs
FIGURE 6.8
Finder methods can
result in poor performance under BMP.
local
client
1. findManyBy
local
home
247
2. ejbFindManyBy
bean
3. select
where…
PK1
4. getXXX
[for each local proxy]
local1
PK1
.
.
.
.
.
.
PKn
PKn
localn
5. ejbLoad
7. getXXX
6. select…
bean
8. ejbStore
9. update…
• Another technique that can be used is to alter the definition of the primary key
class. As well as holding the key information that identifies the bean in the database, it also holds the rest of the bean’s data as well. When the original finder bean
returns the Collection of primary keys, the primary keys are held by the local
proxies. When the beans are activated, they can obtain their state from the proxy by
using entityContext.getLocalObject().getPrimaryKey(). This technique has
been dubbed the fat key pattern, for obvious reasons.
• Last, you may be able to remove the performance hit by porting the bean to use
container managed persistence. Because under CMP the EJB container is responsible for all access to the persistent data store, many will obtain all the required
information from the data store during the first findManyByXxx method call, and
then eagerly instantiate beans using this information. You will be learning more
about CMP tomorrow.
Incidentally, Figure 6.8 shows a graphic illustration of why Entity beans should, in general, define only a local interface—not a remote interface. If the client were remote rather
than local, the total network calls for a finder method returning references to 20 beans
would be double the original figure, namely 42! Moreover, every subsequent business
method invocation (call to getYYY() for example) would inflict a further 20 network calls.
EJB Container Performance Tuning
Many organizations are wary of using Entity beans because of the performance costs that
are associated with it. You have already seen the performance issues arising from using
finder methods, but even ignoring this, any business method to an Entity bean will
require two database calls—one resulting from the ejbLoad() that precedes the business
method and one from the ejbStore() to save the bean’s new state back to the data store.
6
08 0672323842 CH06
248
3/20/02
9:31 AM
Page 248
Day 6
Of course, these database calls may be unnecessary. If a bean hasn’t been passivated
since the last business call, the ejbLoad() need not do anything, provided that nothing
has updated the data store through non-EJB mechanisms. Also, if the business method
called did not change the state of the bean the ejbStore() has nothing to do also.
Another scenario is where a bean is interacted with several times as part of a transaction.
You will be learning more about transactions on Day 8, so for now, just appreciate that
when a bean is modified through the course of a transaction, either all of its changes in
state or none of them need to be persisted. In other words, there is only really the
requirement to call ejbStore() just once at the end of the transaction.
Taking these points together, the amount of network traffic from the EJB container to the
persistent data store can be substantially reduced, down to the levels that might be
expected in a hand-written J2SE client/server application. Although not part of the EJB
specification, many EJB containers provide proprietary mechanisms to prevent unnecessary ejbLoad() or ejbStore() calls. Of course, the use of these mechanisms will make
your bean harder to port to another EJB container, but you may well put up with the
inconvenience for the performance gains realized. Indeed, if you are in the process of
evaluating EJB containers, as many companies are, you may even have placed these features on your requirements list.
Configuring and Deploying a BMP
Entity Bean
Yesterday, you learned how to deploy Session beans by creating ejb-jar files with their
own deployment descriptor and including the ejb-jar into an enterprise application.
Deploying Entity beans is done in precisely the same way, by creating ejb-jar files that
contain the Entity bean classes, with appropriate entries in the deployment descriptor.
This deployment descriptor is the same deployment descriptor as for Session beans. As
you saw yesterday, the root element of this deployment descriptor is the ejb-jar element:
<!ELEMENT ejb-jar (description?, display-name?, small-icon?, large-icon?,
enterprise-beans, relationships?, assembly-descriptor?, ejb-client-jar?)>
Looking at the enterprise-beans element, this is defined as follows:
<!ELEMENT enterprise-beans (session | entity | message-driven)+>
The deployment descriptor can contain Session, Entity, and/or Message-driven beans. Or
put another way, Session beans and Entity beans can be placed in the same ejb-jar if
required.
08 0672323842 CH06
3/20/02
9:31 AM
Page 249
Entity EJBs
249
Entity Element
The entity element of the DTD is defined as follows:
<!ELEMENT entity (description?, display-name?, small-icon?, large-icon?,
ejb-name, home?, remote?, local-home?, local?, ejb-class,
persistence-type,
prim-key-class,
reentrant,
cmp-version?, abstract-schema-name?,
cmp-field*, primkey-field?,
env-entry*,
ejb-ref*, ejb-local-ref*,
security-role-ref*, security-identity?,
resource-ref*,
resource-env-ref*,
query*)>
Many of the elements referred by the entity element are also used by the session element, and were discussed yesterday:
• Presentational elements—description, display-name, small-icon, and largeicon.
• Elements describing the components of the bean—ejb-name, home, remote, localhome, local, and ejb-class elements. The local-home and local elements just
name the interfaces that extend javax.ejb.EJBLocalHome and
javax.ejb.EJBLocal, respectively.
• Elements referring to the environment and other resources—env-entry, ejb-ref,
ejb-local-ref, resource-ref, and resource-env-ref.
The remaining elements are specific to Entity beans:
•
persistence-type
Set to Bean for bean-managed persistence or Container for
container-managed persistence. For the purposes of today, this will be set to Bean.
•
This mandatory element indicates the name of the primary key
class. This will be a custom developed class for beans that have primary keys
(JobPK for Job bean) but may be a regular class (java.lang.String) for others
(for example, the Location bean).
•
cmp-version, abstract-schema-name, cmp-field, prim-key-field, query
prim-key-class
These are used only for CMP beans and are covered tomorrow.
•
reentrant
Set to true for reentrant Entity beans, or false for non reentrant
beans. It’s safer to mark Entity beans as non reentrant.
•
security-role-ref, security-identity
Day 15.
You will learn more about security on
6
08 0672323842 CH06
3/20/02
250
9:31 AM
Page 250
Day 6
Listing 6.12 shows the deployment descriptor for the Job bean.
LISTING 6.12
entity Element Descriptor for the Job Bean
1: <entity>
2:
<display-name>JobBean</display-name>
3:
<ejb-name>JobBean</ejb-name>
4:
<local-home>data.JobLocalHome</local-home>
5:
<local>data.JobLocal</local>
6:
<ejb-class>data.JobBean</ejb-class>
7:
<persistence-type>Bean</persistence-type>
8:
<prim-key-class>data.JobPK</prim-key-class>
9:
<reentrant>False</reentrant>
10:
<ejb-local-ref>
11:
<ejb-ref-name>ejb/SkillLocal</ejb-ref-name>
12:
<ejb-ref-type>Entity</ejb-ref-type>
13:
<local-home>data.SkillLocalHome</local-home>
14:
<local>data.SkillLocal</local>
15:
<ejb-link>data_entity_ejbs.jar#SkillBean</ejb-link>
16:
</ejb-local-ref>
17:
<ejb-local-ref>
18:
<ejb-ref-name>ejb/LocationLocal</ejb-ref-name>
19:
<ejb-ref-type>Entity</ejb-ref-type>
20:
<local-home>data.LocationLocalHome</local-home>
21:
<local>data.LocationLocal</local>
22:
<ejb-link>data_entity_ejbs.jar#LocationBean</ejb-link>
23:
</ejb-local-ref>
24:
<ejb-local-ref>
25:
<ejb-ref-name>ejb/CustomerLocal</ejb-ref-name>
26:
<ejb-ref-type>Entity</ejb-ref-type>
27:
<local-home>data.CustomerLocalHome</local-home>
28:
<local>data.CustomerLocal</local>
29:
<ejb-link>data_entity_ejbs.jar#CustomerBean</ejb-link>
30:
</ejb-local-ref>
31:
<security-identity>
32:
<description></description>
33:
<use-caller-identity></use-caller-identity>
34:
</security-identity>
35:
<resource-ref>
36:
<res-ref-name>jdbc/Agency</res-ref-name>
37:
<res-type>javax.sql.DataSource</res-type>
38:
<res-auth>Container</res-auth>
39:
<res-sharing-scope>Shareable</res-sharing-scope>
40:
</resource-ref>
41: </entity>
Note the resource-ref dependency on jdbc/Agency, just as you saw for Session beans
that make JDBC calls. The res-ref-name is the coded name jdbc/Agency that appears
in the setEntityContext() method of LocationBean. This logical reference is mapped
08 0672323842 CH06
3/20/02
9:31 AM
Page 251
Entity EJBs
251
to the physical resource through the auxiliary deployment descriptor agency_ea-sunThere are also ejb-ref dependencies on various other beans; these are
ultimately used to manage the relationships with the Location, Skill, and Customer
beans.
j2ee-ri.xml.
All of this information can be seen through the J2EE RI deploytool GUI, as shown in
Figure 6.9.
FIGURE 6.9
The deploytool GUI
displays deployment
descriptor information
graphically.
Good though the deploytool GUI is, sometimes a command-line interface is required.
For example, you might want to automatically compile and then re-deploy your enterprise application in a project as part of a nightly build. Luckily, the deploytool also provides a command-line interface, so today you will deploy the Entity beans using this
mechanism.
Under day06\agency, the directory structure has been organized into a number of subdirectories, as shown in Table 6.2.
TABLE 6.2
Directory Structure under day06\agency
Subdirectory
Contents
build
a) buildAll, which calls most of the other scripts in this directory, except
b) deploy, which deploys the enterprise application.
src
Java source for the client, agency, and data packages.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 252
252
Day 6
TABLE 6.2
Continued
Subdirectory
Contents
dd
Standard XML deployment descriptors for the EJBs, for the enterprise application, and for the application clients. This directory also contains auxiliary
deployment descriptors used by the J2EE RI to map the logical references in
the standard deployment descriptors to the physical runtime environment managed by the J2EE RI container.
classes
Compiled Java classes. The various compile* scripts in the build directory
compile into this directory.
jar
run
and ear (Enterprise Application) archives. Generated by the various
scripts in the build directory.
ejb-jar
build*
The scripts to run the various clients.
To deploy the enterprise application, start Cloudscape and the J2EE RI, and then type
> cd day06\agency\build
> buildAll
> deploy
It’s as simple as that!
One of the benefits of this approach is that bean developers, application assemblers and
deployers who are familiar with the standard EJB deployment descriptors can modify the
bean’s deployment configuration by simply editing the XML deployment descriptors
directly—without having to get to grips with the J2EE RI GUI. Moreover, some of the
names automatically assigned by the deploytool GUI, such as the names of the ejb-jar
files themselves, can be given more descriptive names. Consequently, the ejb-jar that
contains the Entity beans is called data_entity_ejbs.jar and its deployment descriptor
is called data_entity_ejbs_ejb-jar.xml.
If you look in the dd directory (or in the deploytool GUI as shown in Figure 6.9), you
can see that the case study separates out the Session beans and the Entity beans into two
different ejb-jars. The Agency ejb-jar contains the same set of Session beans that you
saw yesterday (although their implementation is different as you shall see shortly, they
now delegate to the Entity beans), while the Data ejb-jar has the new BMP Entity
beans. How you choose to organize your enterprise application is up to you.
Client’s View
You’ve now learned how to specify, implement, and deploy BMP Entity beans, but how
are they used? As you can probably imagine, the steps to obtain a reference to an Entity
bean are similar to that for Session beans:
08 0672323842 CH06
3/20/02
9:31 AM
Page 253
Entity EJBs
253
1. Look up the home interface for the Entity bean from JNDI.
2. To create a new entity instance, use the relevant home.create() method.
3. To locate an existing entity instance, use home.findByPrimaryKey() if the primary
key is known, or some other home.findXxx() finder method to obtain a
Collection of matching entities.
4. For the returned local proxy to the Entity bean, invoke the business methods
defined in the local interface of the bean.
The javax.ejb.EJBLocalObject interface also defines a number of other methods that
can be called by the client, and it is worth discussing the semantics of these briefly:
• The getPrimaryKey() method of EJBLocalObject returns the primary key that
identifies the bean.
Caution
Note that because EJBLocalObject is also the super-interface for Session
bean interfaces, this method can also be called when the client has a reference to the local proxy of a Session bean. However, because primary keys do
not make sense for Session beans, an EJBException will always be thrown.
• If an Entity bean has both a local and a remote interface, and then
EJBObject.getPrimaryKey() (from the remote proxy) and
EJBLocalObject.getPrimaryKey() (from the local proxy) will both return objects
that are equal (according to the definition the primary key’s definition of
equals()).
• The isIdentical() method can be used instead of comparing primary key classes
to determine if two bean references refer to the same Entity bean. In other words,
bean1.isIdentical(bean2) returns true if and only if
bean1.getPrimaryKey().equals(bean2.getPrimaryKey()).
One scenario that can occur is that a client can have a reference to an Entity bean, and
then the bean could be deleted by some other client. This could occur either by an EJB
application client that invokes remove() on the same Entity bean, or it could be a nonEJB client that deletes the data directly from the underlying persistent data store. Either
way, the original client will not be notified of this, and won’t detect this situation until it
next invokes a method on the Entity bean. In this case, the client will receive a
javax.ejb.NoSuchObjectLocalException (a subclass of javax.ejb.EJBException, in
turn a subclass of java.lang.RuntimeException) if accessing the Entity bean through
its local interface.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 254
254
Day 6
The javax.ejb.NoSuchObjectLocalException caught by local clients is analogous to
the java.rmi.NoSuchObjectException that would be caught if accessing the Entity bean
through its remote interface. Table 6.3 shows the table from yesterday detailing various
other exceptions, supplemented with the exception classes received by local clients.
TABLE 6.3
System Exceptions Are Thrown in a Variety of Situations
What
Event
Local Client
Receives
Remote Client
Receives
Any bean
Throws javax.ejb.
EJBException (or any subclass)
javax.ejb.
java.rmi.
EJBException
RemoteException
(or subclass)
BMP Entity
bean
Container
javax.ejb.
java.rmi.
NoSuchEntity
NoSuchObject
Exception
Exception
When client invokes method on
a reference to a bean that no
longer exists
javax.ejb.
java.rmi.
NoSuchObject
NoSuchObject
LocalException
Exception
When client calls a method
without a transaction context
javax.ejb.
javax.transaction.
Throws NoSuchEntityException
When client has insufficient
security access
TransactionRequired
TransactionRequired
LocalException
Exception
javax.ejb.
java.rmi.
AccessLocal
AccessException
Exception
When transaction needs to be
rolled back
javax.ejb.
javax.transaction.
TransactionRolled
TransactionRolled
Back LocalException
Back Exception
As you can see, the EJB Specification makes some attempt at a naming standard so that
the models are as similar as possible for local and remote clients.
Session Beans Revisited
The case study for today has the same set of Session beans as yesterday, and their interfaces are the same. However, their implementation is quite different, because they delegate all database interactions to the Entity bean layer.
08 0672323842 CH06
3/20/02
9:31 AM
Page 255
Entity EJBs
255
As an example, Listing 6.13 shows the original updateDetails() method in the stateful
AdvertiseJob bean. The AdvertiseJob bean provides services for managing jobs.
LISTING 6.13
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:
AdvertiseJobBean.updateDetails() Without an Entity Bean Layer
package agency;
import java.util.*;
import javax.ejb.*;
import java.sql.*;
// imports omitted
public class AdvertiseJobBean extends SessionBean
{
public void updateDetails(String description,
➥String location, String[] skills) {
if (skills == null) {
skills = new String[0];
}
Connection con = null;
PreparedStatement stmt = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
➥”UPDATE JOB
➥ SET description = ?, location = ?
➥ WHERE ref = ? AND customer = ?”);
stmt.setString(1, description);
stmt.setString(2, location);
stmt.setString(3, ref);
stmt.setString(4, customer);
stmt.executeUpdate();
stmt = con.prepareStatement(
➥”DELETE FROM JobSkill
➥ WHERE job = ? AND customer = ?”);
stmt.setString(1, ref);
stmt.setString(2, customer);
stmt.executeUpdate();
stmt = con.prepareStatement(
➥”INSERT INTO JobSkill (job, customer, skill)
➥ VALUES (?, ?, ?)”);
for (int i = 0; i < skills.length; i++) {
stmt.setString(1, ref);
stmt.setString(2, customer);
stmt.setString(3, skills[i]);
stmt.executeUpdate();
}
this.description = description;
this.location = location;
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 256
256
Day 6
LISTING 6.13
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48: }
Continued
this.skills.clear();
for (int i = 0; i < skills.length; i++)
this.skills.add(skills[i]);
}
catch (SQLException e) {
error(“Error updating job “ + ref + “ for “ + customer, e);
}
finally {
closeConnection(con, stmt, null);
}
}
Listing 6.14 shows the updated version, delegating the hard work to the Job bean:
LISTING 6.14
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:
AdvertiseJobBean.updateDetails() with an Entity Bean Layer
package agency;
import java.util.*;
import javax.ejb.*;
import data.*;
// imports omitted
public class AdvertiseJobBean extends SessionBean
{
private JobLocal job;
public void updateDetails(String description,
➥String locationName, String[] skillNames) {
if (skillNames == null) {
skillNames = new String[0];
}
List skillList;
try {
skillList = skillHome.lookup(Arrays.asList(skillNames));
} catch (FinderException ex) {
error(“Invalid skill”, ex); // throws an exception
return;
}
LocationLocal location = null;
if (locationName != null) {
try {
location = locationHome.findByPrimaryKey(locationName);
} catch (FinderException ex) {
error(“Invalid location”, ex); // throws an exception
return;
}
}
08 0672323842 CH06
3/20/02
9:31 AM
Page 257
Entity EJBs
LISTING 6.14
31:
32:
33:
34:
35:
36: }
257
Continued
job.setDescription(description);
job.setLocation(location);
job.setSkills(skillList);
}
// code omitted
The updated version is much more object-oriented; the knowledge of the database
schema has been encapsulated where it rightfully belongs—in the Entity bean layer.
All this means that the AdvertiseJob bean no longer has any dependencies on the
On the other hand, it does now have dependencies on several
of the Entity beans. These are defined using ejb-local-ref elements in the deployment
descriptor. The relevant portion of the AdvertiseJob deployment descriptor
(agency_session_ejbs_ejb-jar.xml file in the dd directory) is shown in Listing 6.15:
jdbc/Agency DataSource.
LISTING 6.15
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:
AdvertiseJob Bean’s Reference to the Entity Beans
<ejb-local-ref>
<ejb-ref-name>ejb/SkillLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>data.SkillLocalHome</local-home>
<local>data.SkillLocal</local>
<ejb-link>data_entity_ejbs.jar#SkillBean</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/LocationLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>data.LocationLocalHome</local-home>
<local>data.LocationLocal</local>
<ejb-link>data_entity_ejbs.jar#LocationBean</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/JobLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>data.JobLocalHome</local-home>
<local>data.JobLocal</local>
<ejb-link>data_entity_ejbs.jar#JobBean</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/CustomerLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>data.CustomerLocalHome</local-home>
<local>data.JobLocal</local>
<ejb-link>data_entity_ejbs.jar#CustomerBean</ejb-link>
</ejb-local-ref>
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 258
258
Day 6
Note the ejb-link reference, which names the bean that implements the required interfaces. This notation is used rather than a JNDI name because JNDI can (potentially) refer
to remote EJBs, whereas local EJBs must—by definition—be deployed on the same
server.
Patterns and Idioms
Yesterday, you saw some patterns and idioms that apply to writing Session beans. In this
section, you will see some more that relate to BMP Entity beans and local interfaces. If
you are impatient to get onto the exercise, feel free to skip this section and revisit later.
Interfaces, Façades, and State
This is a pattern that relates mostly to Session beans, but is discussed today rather than
yesterday because it uses local interfaces.
While Session beans can provide both a remote and a local interface, you’ll only want to
provide one or the other more often than not. Generally, you should aim to have a small
number of Session beans that offer a remote interface, and the remainder will be local
“helper” beans. In design pattern terms, the remote Session beans create a Façade. This
pattern is discussed more fully on Day 18, “Patterns.”
The Façade beans will likely be stateful. Certainly, the helper beans should not be stateful, because chaining stateful objects is poor design, as noted yesterday.
Use Local Interfaces for Entity Beans
Entity beans should only ever be accessed through a local interface, and there are some
very good reasons for this.
First, accessing Entity beans through a remote interface implies network traffic and the
cost of cloning serializable objects. When the client runs in the same JVM as the Entity
bean, a local interface eliminates cost.
This ties in with the previous discussion on Session bean interfaces and façades. Session
beans provide a remote interface to the enterprise application, so Session beans should
act as a front-end to Entity beans. Some people think of this as the verb/noun
paradigm—the Session beans are the verbs (the doing end of the application) and the
Entity beans are the nouns (the reusable business objects within some domain). Defining
only local interfaces to Entity beans effectively enforces this pattern.
Second, finder methods of Entity beans—already expensive to use—become even more
so when the client is remote. This was remarked on earlier today.
08 0672323842 CH06
3/20/02
9:31 AM
Page 259
Entity EJBs
259
Lastly, and perhaps most significantly, local interfaces are the cornerstone of containermanaged relationships (CMR)—part and parcel of container-managed persistence. You’ll
be learning all about this tomorrow. If you build your BMP beans using local interfaces,
you provide a migration path to implementing those beans using CMP in the future.
Dependent Value Classes
Under BMP, the Entity bean’s state can be represented in any way that is appropriate.
Moreover, the bean can persist this state, in any way it wants.
Sometimes, a bean’s state will be simple enough (that is, just a set of scalar fields) that it
will correspond to a row in an RDBMS table. More often though, some of those fields
will be vector, and they might even include some complex data, such as a map or photo
and so on.
One simple solution to persisting such objects is to ensure that the fields of the bean are
either scalar primitives or are a reference to a serializable object, an instance of what are
sometimes called dependent value classes. The structure of that object can be as complex
as needed, so long as it is serializable. The scalar fields are stored in regular columns in
the RDBMS table, and the serializable object is stored as a binary large object (BLOB).
For example, the Job bean is mapped to both the Job and JobSkill table in the case
study, accessing both in its ejbLoad() and ejbStore() methods. The JobBean.skills
field is a List of SkillLocal references.
An alternative design would be to store the skills list as a BLOB in the database. The
Job table would be redefined to be something like the following:
create table Job
(ref varchar(16),
customer varchar(16),
description varchar(512),
location varchar(16),
skills long varbinary
)
-- store a BLOB in Cloudscape
There is then a one-to-one mapping between an instance of the Job Entity bean and a
row in the Job table. One slight complication is that because the JobBean.skills field
contains references to SkillLocal references, which are not necessarily serializable, the
skills variable would not be serializable either. So, in the ejbStore() method, a List
of skill names (that is, primary key to each SkillLocal) would be created and saved to
the database. This List of skill names is the “dependent value class.”
stmt = con.prepareStatement(
➥”UPDATE Job
➥ SET description = ?, location = ?, skills = ?
➥ WHERE ref = ? AND customer = ?”);
6
08 0672323842 CH06
260
3/20/02
9:31 AM
Page 260
Day 6
stmt.setString(1, description);
if (location != null) {
stmt.setString(2, location.getName());
} else {
stmt.setNull(2, java.sql.Types.VARCHAR);
}
List skillNameList = new ArrayList();
for(Iterator iter = this.skills.iterator(); ) {
skillNameList.add( ( (SkillLocal)iter.next() ).getName() );
}
stmt.setBlob(3, skillNameList);
stmt.setString(4, ref);
stmt.setString(5, customerObj.getLogin());
stmt.executeUpdate();
Conversely, in the ejbLoad(), the List of skill names would be converted back to a List
of SkillLocal references. The SELECT statement would be as follows:
stmt = con.prepareStatement(
“SELECT description,location,skills FROM Job WHERE ref = ? AND customer = ?”);
and the processing of the result set would be
this.description = rs.getString(1);
String locationName = rs.getString(2);
this.location = (locationName != null)?
➥locationHome.findByPrimaryKey(locationName):null;
List skillNameList = (List)rs.getBlob(3);
this.skills =
skillHome.lookup(skillNameList);
The skillHome.lookup() home method of the Skill bean does the actual conversion
from name to SkillLocal. (In fact, this method is actually used in JobBean’s ejbLoad()
method, so you can check out the code yourself).
This approach can substantially reduce the coding effort, although you should also be
aware of some of the downsides:
• First, the BLOB field is effectively atomic; even if just a small piece of information is
changed (for example, a new skill is added), and then entire BLOB must be replaced.
• Furthermore, information previously easily accessible can now only be accessed
through a single route. In the previous example, the data that was previously in the
JobSkill table is now buried within the Job.skills field. It is no longer possible
to perform an efficient SQL SELECT to find out which jobs require a certain skill.
Instead, such a query will involve instantiating and then querying every Job bean
instance.
08 0672323842 CH06
3/20/02
9:31 AM
Page 261
Entity EJBs
261
• Last, the data in the persistent data store is stored in Java’s own serializable format.
While this is a well-defined structure, it nevertheless makes it non-trivial for nonJava clients to access this data.
Note
It is possible to serialize Java classes in a custom-defined manner (for example, as an XML string) by either providing a readObject() and
writeObject() method or by implementing java.io.Externalizable and
implementing readExternal() and writeExternal() methods. However,
such an approach would probably involve more development effort than
had been saved by using a BLOB to store the dependent value class.
Because dependent value classes are serializable, it is also possible to use them as the
return types of accessor methods (getters and setters) of the interface. Indeed, prior to the
introduction of local interfaces in EJB 2.0, this was a recommended pattern to minimize
network traffic across the Entity bean’s remote interface. However, provided that only
local interfaces are provided (and especially if using CMP), there is nothing wrong with
providing fine-grained access to the values of the Entity bean. In effect, this design pattern has been deprecated with EJB 2.0.
Self-Encapsulate Fields
In the case study BMP beans, the private fields that represent state are accessed directly
within methods. For example, the following is a fragment of the JobBean.ejbCreate()
method:
public JobPK ejbCreate (String ref, String customer) throws CreateException {
// database access code omitted
this.ref = ref;
this.customer = customer;
this.description = description;
this.location = null;
this.skills = new ArrayList();
// further code omitted
}
Some OO proponents argue that all access to fields should be through getter and setter
accessor methods, even for other methods of the class. In other words, the principle of
encapsulation should be applied everywhere. Using such an approach, the ejbCreate()
method would be as follows:
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 262
262
Day 6
public JobPK ejbCreate (String ref, String customer) throws CreateException {
// database access code omitted
setRef(ref);
setCustomer(customer);
setDescription(description);
setLocation(null);
setSkills(new ArrayList());
// further code omitted
}
Some people find this overly dogmatic, and, indeed, the code in the case study takes the
more direct approach. However, you may want to consider self-encapsulation because it
makes BMP beans easier to convert to CMP. As you will see tomorrow, all accessing to
the Entity bean’s state must be through accessor methods.
Don’t Use Enumeration for Finders
The EJB 1.0 specification was introduced before J2SE 1.2, so the specification allowed
finder methods that returned many instances to return java.util.Enumeration
instances. For backward compatibility, the EJB 2.0 specification still supports this, but
you should always use java.util.Collection as the return type for finder methods
returning more than one Entity bean instance.
Acquire Late, Release Early
In conventional J2SE programs, the idiom usually is to connect to the database at the
start of the program when the user logs in, and only disconnect when the user logs out.
Holding onto the open database connection while the user logs in substantially improves
performance; database connections are relatively expensive to obtain. So, for J2SE programs, the mantra is “Acquire early, release late.”
With J2EE programs, things are inverted. The database connection should be obtained
just before it is required, and closed immediately after it has been used. In other words,
“Acquire late, release early.” This is shown in the Job bean, as shown in Listing 6.16.
LISTING 6.16
1:
2:
3:
4:
5:
6:
Acquire Late, Release Early, as Shown in JobBean
package data;
import javax.ejb.*;
import java.sql.*;
// imports omitted
08 0672323842 CH06
3/20/02
9:31 AM
Page 263
Entity EJBs
LISTING 6.16
263
Continued
7: public class JobBean implements EntityBean
8:
9:
public void ejbLoad() {
10:
JobPK key = (JobPK)ctx.getPrimaryKey();
11:
Connection con = null;
12:
PreparedStatement stmt = null;
13:
ResultSet rs = null;
14:
try {
15:
con = dataSource.getConnection();
16:
stmt = con.prepareStatement( … );
17:
18:
// SQL code omitted
19:
20:
}
21:
catch (SQLException e) {
22:
error(“Error in ejbLoad for “ + key, e);
23:
}
24:
catch (FinderException e) {
25:
error(“Error in ejbLoad (invalid customer or location) for “
➥ + key, e);
26:
}
27:
finally {
28:
closeConnection(con, stmt, rs);
29:
}
30:
}
31:
// code omitted
32: }
The reason that this works is because the database connection is obtained from a
javax.sql.DataSource (line 15) in a J2EE environment, rather than using the
java.sql.DriverManager.getConnection() method. Obtaining connections from
DataSources is not expensive in performance terms because they are logical connections,
not physical connections. When such a connection is obtained, it is merely obtained from
a connection pool, and when it is “closed,” it is simply returned back to the connection
pool.
Indeed, using the J2SE idiom of acquire early, release late (for example, by obtaining a
connection in setEntityContext() and releasing it in unsetEntityContext()) can
adversely affect performance, because every instantiated bean would have its own database connection. This may well reduce application throughput because the memory
resources of both the EJB container and the database server would be increased to handle
many open database connections. In comparison, the J2EE idiom means that the number
of database connections open is no more than the number of methods concurrently executing.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 264
264
Day 6
Business Interface Revisited
Yesterday, you learned about the business interface idiom, whereby the business methods
are pulled out into a separate interface such that the bean itself can implement this interface. This principle can equally be applied to Entity beans using local interfaces, as
shown in Figure 6.10.
FIGURE 6.10
Business interfaces can
be applied to Entity
beans with local interfaces.
interface
EJBLocalHome
interface
EJBLocalObject
interface
data.JobBus
remove
getEJBLocalHome
getPrimaryKey
remove
isIdentical
getRef
getCustomer
getCustomerObj
setDescription
getDescription
setLocation
getLocation
getSkills
setSkills
interface
data.JobLocalHome
interface
data.JobLocal
create
findByPrimaryKey
findByCustomer
findByLocation
deleteByCustomer
attributes, EJB lifecycle methods
and private helper methods
have been hidden.
data.JobBean
getRef
getCustomer
getCustomerObj
getDescription
setDescription
getLocation
setLocation
getSkills
setSkills
There is one difference when applying this technique to beans that only have local interfaces; there is no longer any need for the methods of the business interface to throw
RemoteException, because the local interface of the bean (JobLocal in Figure 6.10) is
not itself remote. Even so, the bean still does not implement its local interface because
the methods of the EJBLocalObject interface are there to be implemented by the local
proxy object, not by the bean.
Gotchas
The following is a quick checklist of “gotchas” to help you with your implementation:
• Primary keys must be immutable. In other words, it is not possible to change the
value of a primary key for an entity once assigned (see EJB specification, section
10.3.5).
Of course, there is nothing to prevent you from directly changing the data in the
underlying persistent data store (for example, with an SQL UPDATE statement). But
you will need to do this with the EJB container offline or otherwise at rest.
08 0672323842 CH06
3/20/02
9:31 AM
Page 265
Entity EJBs
265
• Sometimes, Entity beans interact with non-data store resources. An example might
be a client java.net.Socket or perhaps a subscription to a JMS topic (covered
more on Days 9, “Java Messaging Service,” and Day 10, “Message-Driven
Beans”). These resources will need to be acquired in both ejbActivate() (for an
existing bean) and also for ejbCreate() (if the bean has just been created).
Similarly, resources should be released in both ejbPassivate() and ejbRemove().
This is because a bean being deleted will not be passivated first.
• Finder methods can return Collections, but they can’t return Lists, Sets, or Maps.
However, this capability is planned for future versions of the EJB specification.
• If you have two bean references, note that the value of bean1.equals(bean2) is
unspecified, and that bean1 == bean2 is also unspecified. Moreover, hashCode()
may differ for two references to the same underlying EJB. (All of these points are
made in the EJB specification, section 9.8.)
The correct way to compare bean identity is to use bean.isIdentical() or to use
the equals() method on the primary key classes.
• Beware of a reliance on pass-by-reference side-effects when using local interfaces.
Such a reliance would compromise portability.
Summary
Another long day, but you now have lots of good new material under your belt. You’ve
learned that Entity beans represent persistent domain data with corresponding domain
(not application) logic. You’ve seen that the constituent parts of Entity beans are pretty
much the same as Session beans, though Entity beans also require a primary key class
that must be custom-developed if the key is composite.
You’ve also learned that there are two different ways to implement Entity beans, either
using bean-managed persistence, whereby the persistence code (JDBC, for example)
resides within the bean code, or using container-managed persistence. You now know the
lifecycle for BMP beans and how to implement such beans.
You saw that the EJB specification allows local interfaces to be defined for EJBs, as well
as or instead of remote interfaces, and saw several good reasons why Entity beans should
always use local interfaces.
Onto deployment, you now know that the J2EE RI allows EJBs can be deployed using a
command line interface. A deeper understanding of the XML deployment descriptor is
needed, but the process for deployment is (arguably) more portable and faster.
Finally, you’ve learned numerous design techniques, patterns, and idioms that should set
you up for designing and implementing Entity beans effectively.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 266
266
Day 6
Q&A
Q What do Entity beans represent?
A Entity beans represent persistent data that can be accessed and shared by many
clients over time.
Q What are the two types of Entity beans?
A The two types of Entity beans are BMP and CMP.
Q Why are local interfaces preferable to remote interfaces for Entity beans?
A Local interfaces perform better because there is no network traffic when calling a
bean through its local interface, and there is also no need to clone serializable
objects. They are also the basis for CMP.
Q How does a BMP Entity bean know what its primary key is?
A It can be passed as an argument of ejbCreate(), it could be generated by the
RDBMS, it could be generated by some other bean, or it might be generated as a
pseudo-random value using an algorithm that guarantees uniqueness.
Q Which two methods should the primary key class implement?
A The primary key class should implement the hashCode() and equals() methods.
Exercises
The exercise starts with a version of today’s case study that has a complete set of Session
beans, but an incomplete set of Entity beans. Where there is no Entity bean, the Session
bean performs direct SQL. The state of affairs is shown in Table 6.4.
TABLE 6.4
Case Study Session and Entity Beans
Session
Bean
Functional
Area
Functions
Implementation/
Delegation
Agency
Applicants
create, delete, find all
Direct SQL
Customers
create, delete, find all
Customer
bean
Locations
add, get details, get plural, remove
Location
bean
Skills
add, get details, get plural, remove
Skill
Job
create, delete, get plural
Job
Customer
get details, update
Customer
AdvertiseJob
Job
get details, update
Skill
Register
Applicant
get details, update
Direct SQL
Advertise
bean
bean
bean
bean, Location bean
08 0672323842 CH06
3/20/02
9:31 AM
Page 267
Entity EJBs
267
The exercise is to implement an Applicant Entity bean and to update the Agency and
Register Session beans to use this new Entity bean.
The Applicant bean should map itself to the Applicant and ApplicantSkill tables and
define the following fields:
•
login
This is the primary key for the Applicant Entity bean.
•
name
•
email
•
summary
•
location
•
skills
Simple scalar field.
Simple scalar field.
Simple scalar field.
Should be a reference to a LocationLocal to ensure referential integrity.
Should be a collection of SkillLocal references to ensure referential
integrity.
You should find that the structure of your new bean shares many similarities with the Job
Entity bean. One difference will be the primary key. The Job bean required a JobPK
because it had a composite primary key. For your Applicant bean, you should not need
to develop a custom primary key class because applicants will be identified simply by
their login—a simple String.
The ApplicantLocalHome and ApplicantLocal interfaces have already been provided;
note their similarity to JobLocalHome and JobLocal.
The directory structure of day06\exercise is the same as yesterday:
•
src
The source code for the EJBs and clients.
•
classes
•
dd
•
build
•
jar
Directory to hold the compiled classes; empty.
Holds XML deployment descriptors.
Batch scripts (for Windows and UNIX) to compile the source and to build
the EAR files into the jar directory.
Holds agency.ear: the agency enterprise application. Also holds
the client-side JAR file optionally generated when deploy
EAR. This directory also holds some intermediary JAR files that are used only to
create the previous two jar files.
agencyClient.jar,
•
run
jar
Batch scripts (for Windows and UNIX) to run the JARs. Use the files in the
directory.
In the detailed steps that follow, note one difference from yesterday is that today you
will be defining and configuring the EJB as part of the enterprise application by directly
editing the XML deployment descriptors in the dd directory. If you feel uneasy about
doing this, there is nothing to prevent you from making the changes through the GUI.
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 268
268
Day 6
Do note, however, that the build scripts that create the agency.ear file do require that the
ApplicantBean.java source exists (even if its implementation is incomplete).
The steps you should follow are:
1. Locate the ApplicantBean.java file within day06\exercise\src\data. This
should have an empty implementation.
2. Implement ApplicantBean to support the Applicant and ApplicantLocalHome
interfaces supplied. Base your implementation on JobBean, if you want.
3. Next, modify the AgencyBean Session bean. The findAllApplicants(),
createApplicant(), and deleteApplicant() methods should instead delegate to
ApplicantHome.
4. Now update the RegisterBean Session bean. In its ejbCreate() method, it should
obtain a reference to an underlying Applicant Entity bean. Each of the business
methods should then delegate to this applicant. If you want something to work
from, look at the approach adopted by the AdvertiseJob Session bean, delegating
to an instance of Job Entity bean.
5. Update the data_entity_ejbs_ejb-jar.xml deployment descriptor in the dd
directory; again, cloning and adapting the Job bean entries will be a good start.
6. Update the agency_session_ejbs_ejb-jar.xml deployment descriptor to indicate
the new dependencies of the Agency and Register Session beans. Both will
depend on ApplicantLocal; you should also find that Register depends on
SkillLocal and LocationLocal (to call the business methods of Applicant).
7. The buildDataEntityEjbs script already references ApplicantBean, so there is no
need to change it. This causes your classes to be added to the resultant
data_entity_ejbs.jar ejb-jar file.
8. Now, build the jar\agency.ear enterprise application by using build\buildAll.
Load the resultant EAR file into deploytool, and check that the EJB is correctly
defined. If it is not, either make the appropriate changes and run buildAll or make
the changes through the deploytool GUI itself. Then, save the deployment
descriptors into the dd directory.
9. Your agency.ear file is not quite ready to deploy, because the vendor-specific
mapping information has not yet been specified. This is most easily generated by
deploying the enterprise application from deploytool. The wizard that then
appears will ensure that you have the opportunity to indicate any missing information. Then, test by using the AllClients client, invoked using the run\runAll
script.
08 0672323842 CH06
3/20/02
9:31 AM
Page 269
Entity EJBs
269
10. Optionally, you may want to save the auxiliary deployment descriptor to
dd\agency_ea-sun-j2ee-ri.xml. If you do this, you will be able to build and
deploy the application directly from the command line using build\buildAll and
build\deploy, respectively. However, to obtain the auxiliary deployment descriptor, you will need to manually load the agency.ear file (from the previous step)
into WinZip or equivalent and extract the auxiliary deployment descriptor; the
deploytool GUI does not provide any direct mechanism.
Good luck. A working example can be found in day06\agency (with a correct auxiliary
deployment descriptor).
6
08 0672323842 CH06
3/20/02
9:31 AM
Page 270
09 0672323842 CH07
3/20/02
12:20 PM
Page 271
WEEK 1
DAY
7
CMP and EJB QL
Yesterday, you learned how to specify, implement, and deploy bean-managed
persistence (BMP) Entity beans. Today, you will learn
• How to specify, implement, configure and deploy CMP Entity beans
• How to use EJB Query Language (EJB QL)
• How to define relationships between CMP Entity beans
Overview of Container-Managed
Persistence
The EJB specification provides for two different ways of implementing Entity
beans. The first approach, covered yesterday, is for the bean provider to embed
the persistence logic within the bean itself—hence the name bean-managed persistence or BMP. The second is for the container vendor to provide that logic,
either generated by the EJB container vendor’s deployment tools or as part of
the EJB container itself. Entity beans built this way are called CMP Entity
beans.
09 0672323842 CH07
3/20/02
272
12:20 PM
Page 272
Day 7
Note
CMP Entity beans have always been part of the EJB specification, first in EJB
1.0 and then with some minor refinements in EJB 1.1. The changes to CMP
Entity beans in EJB 2.0 are substantial—so substantial, in fact, that CMP 1.1
Entity beans are not forward compatible with EJB 2.0.
To deal with this, the EJB specification actually provides two different ways
to write CMP Entity beans. The first is the legacy 1.1 approach; beans that
are written this way indicate it using an entry in their deployment descriptor. The second is using the new and far more powerful approach introduced in EJB 2.0.
Today, you will be learning only about the new EJB 2.0 approach.
The “anatomy” of CMP Entity beans is very much the same as BMP Entity beans:
• They have a local-home (or remote home) interface that defines the create methods, the finder methods, optional home methods, and a remove method.
• They have a local (or remote) interface that defines the business methods of the
bean.
• Obviously, they have the bean class itself that implements methods corresponding
to the previously mentioned interfaces, and implements the lifecycle methods
inherited from javax.ejb.EntityBean.
• Finally, they may have a primary key class (and must have one if the primary key
is composite).
However, there are some differences. The responsibilities of the bean in the lifecycle
methods are different, because there is no longer any requirement to persist the bean’s
state. This raises the question as to when the state is persisted by the container, because it
could be done either before the lifecycle method is called or after. There are changes in
the interactions between the container and the bean, as you will see.
Another significant difference is the finder methods. Under BMP, the bean provider
writes the appropriate finder methods that interact with the persistent data store. Under
CMP, the container will do this work, so there is no longer any need to implement the
finder methods in the bean. However, the bean provider must still specify the nature of
the query to be performed to obtain the correct data from the data store. This is done
using EJB Query Language (EJB QL), appearing in the bean’s deployment descriptor.
EJB QL shares many similarities with ANSI SQL 92, so you should not have too many
difficulties picking it up.
09 0672323842 CH07
3/20/02
12:20 PM
Page 273
CMP and EJB QL
273
Just as tables in relational databases have relationships, so too do Entity beans. You saw
this yesterday with the relationship between the Job bean, which had relationships with
the Skill, Location, and Customer beans. Under BMP, the bean provider must write the
code that maintains all of these relationships explicitly. If CMP is used, these relationships can be declaratively defined using container-managed relationships, or CMR.
Again, the declarations of these associations are in the bean’s deployment descriptor.
Relationships between Entity beans are intrinsically fine-grained. For example, a manyto-many relationship between Job and Skill (indicating which skills are needed for
such-and-such a job) would involve dealing with many (job,skill) tuples in the case
study stored in the JobSkill table. You know that Entity beans can have either a local or
a remote interface, and that it’s good practice to only ever interact with an Entity bean
through its local interface because this reduces network traffic. Because the performance
cost of maintaining a fine-grained relationship across the network would be too severe,
the EJB specification requires that container-manager relationships between Entity beans
are defined only through local interfaces. Indeed, one of the primary reasons for the
introduction of local interfaces in the EJB specification was to make CMR feasible.
N-tier Architecture (Revisited Again) and CMP Fields
CMP has an impact on the n-tier architecture that you seen have on several previous
days. Figure 7.1 shows an update of a figure that you saw yesterday.
FIGURE 7.1
CMP Entity beans are
split into two components.
«swing»
user interface
«session EJB»
application logic
«servlet»
user interface
«entity EJB»
domain logic
«bean provider»
CMP bean
«database»
persistence layer
«EJB container»
CMP implementation
«servlet»
user interface
«session EJB»
application logic
There are still four tiers to the architecture—namely, the interface, application, domain,
and persistence layers. However, with CMP, the Entity beans split into two components.
The first component is provided by you, the bean provider. This defines the bean’s
local-home and local interfaces, but the implementation of the bean itself is incomplete.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 274
274
Day 7
It provides a full implementation of the business methods, but there is no implementation
of the accessor methods for the bean’s state. Indeed, you will see that the methods are
marked as abstract. The concrete implementation of the CMP bean is completed by
the EJB container provider. This component has dependencies on both the bean
provider’s bean, and—of course—on the persistence layer. The first dependency is
because the concrete implementation uses the bean provider’s abstract bean class as its
superclass; in other words, it extends from the CMP bean. The second dependency is
because the implementation of the bean performs appropriate data store calls.
You may recognize this design as an instance of the Template design pattern. The
abstract CMP bean provided by the bean provider is a template, defining certain mandatory “hook” methods—namely, the accessor methods. The implementation of these
hooks is provided by the EJB container in terms of the concrete CMP implementation.
Listing 7.1 shows this for the Job Entity bean. This bean defines a pair of accessor methods (the getter and setter) for each of its fields—ref, customer, description, location,
and skills.
LISTING 7.1
The JobBean’s Fields Are Implied by the Presence of These Abstract Accessor
Methods
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public abstract void setRef(String ref);
public abstract String getRef();
public abstract void setCustomer(String customer);
public abstract String getCustomer();
public abstract String getDescription();
public abstract void setDescription(String description);
public abstract LocationLocal getLocation();
public abstract void setLocation(LocationLocal location);
public abstract Collection getSkills();
09 0672323842 CH07
3/20/02
12:20 PM
Page 275
CMP and EJB QL
LISTING 7.1
21:
22:
23:
24: }
275
Continued
public abstract void setSkills(Collection skills);
// code omitted
Each of these accessors is implemented by the concrete CMP implementation. The actual
instance variables are effectively part of the subclass’ implementation, ultimately populated from the persistent data store.
Caution
This naming scheme throws up a very curious restriction, specifically that
cmp-fields must not start with a capital letter. Thus, customer and even
cUSTOMER are valid names, but CUSTOMER would not be. This is because the
methods capitalize the cmp-fields, and one cannot capitalize a capital!
Another way of thinking about this design is in terms of vertical delegation. The Session
beans can call the accessor methods on methods defined in the superclass, but the actual
method that is invoked is the implementation defined in the subclass. The superclass
CMP bean “delegates” vertically down to its subclass CMP implementation.
This design gives several advantages. One immediate advantage of this pattern is that it
gives the EJB container (through the concrete bean implementation) much more control
over populating the bean’s state, without compromising good OO principles. For example, it is up to the EJB container whether it chooses to obtain all of the bean’s state when
the bean is activated or created (eager loading), or whether it chooses to fetch the bean’s
state from the persistent data store as and when needed (lazy loading). Indeed, the concrete implementation may adopt some half-way house, eagerly loading all the scalar data
pertaining to the bean but lazily loading data corresponding to bean relationships.
Indeed, this advantage is pointed out in the EJB specification (section 10.4.2.1).
Another advantage is that the EJB container only need persist the bean’s state to the data
store when the bean’s state has changed. The concrete implementation can keep track of
the before-and-after versions of the bean’s state and compare them to see if any have
changed as the result of a business method invocation. If a read-only accessor method (a
“getter” method) is called, there would be no change in state and so the concrete implementation need not perform an unnecessary update to the data store. Taking this on one
further stage, when the bean’s state is changed, the EJB container need only update those
fields that have changed and can ignore fields that have not changed. This reduces network traffic between the EJB container and the persistent data store.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 276
276
Day 7
One final advantage worth mentioning is that some value-add services, such as optimistic
locking, can be implemented by EJB container vendors more straight-forwardly.
A Quick Word about the Case Study Database
As you go through the remaining topics for today, you may well want to load up and run
the case study code. Before you can do this, the Agency database needs to be modified to
support CMP. This is because the J2EE RI container generates its own SQL schema to
store the Entity beans’ data.
Figure 7.2 shows the revised schema for the case study, as generated by the J2EE RI
container.
FIGURE 7.2
CustomerBeanTable
The case study database schema changes
under CMP.
address1
address2
email
name
login
advertises
JobBeanTable
LocationBeanTable
ApplicantBeanTable
ref
customer CustomerBeanTable.login(FK)
name
login
description
email
name
summary
description
location for
at
workplace for
works at
Z
requires
Z
JobBean_location_LocationBean_Table
ApplicantBean_location_LocationBean_Table
_JobBean_ref JobBeanTable.ref(FK)
_JobBean_customer JobBeanTable.customer(FK)
_ApplicantBean_login ApplicantBeanTable.login(FK)
_LocationBean_name LocationBeanTable.name(FK)
_LocationBean_name LocationBeanTable.name(FK)
has
SkillBeanTable
name
JobBean_skills_SkillBean_Table
_JobBean_ref JobBeanTable.ref(FK)
_JobBean_customer JobBeanTable.customer(FK)
_SkillBean_name SkillBeanTable.name(FK)
description
needed for
ApplicantBean_skills_SkillBean_Table
capabilities of
_SkillBean_name SkillBeanTable.name(FK)
_ApplicantBean_login ApplicantBeanTable.login(FK)
The J2EE RI container can automatically create this schema when the Entity beans are
deployed. However, because there is example data in the case study database, it is easier
to run the provided utility to “upgrade” the database to support CMP. The Agency
Session bean queries the Cloudscape RDBMS tables directly. To ensure that this continues to work, the utility also creates SQL views with the names of the old tables
(JobSkill and so on) against the new tables.
09 0672323842 CH07
3/20/02
12:20 PM
Page 277
CMP and EJB QL
277
The steps for converting the database to support CMP are as follows:
1. Shut down Cloudscape if it is running.
2. Back up the current (pre day 7) version of the Agency database, under
%J2EE_HOME%\cloudscape.
Under Windows, you can do this using copy and paste, or from the command line
type:
> cd %J2EE_HOME%\cloudscape
> xcopy Agency bmpAgency /I /E
Under Unix, you can do this using the following:
$ cd $J2EE_HOME/cloudscape
$ cp –r Agency bmpAgency
3. Restart Cloudscape.
4. Under the case study day07\Database directory, run the batch
CreateCMPAgency.bat (Windows) or CreateCMPAgency.sh (Unix). This calls
CreateCMPAgency.java (already compiled for you) which, in turn, creates the previous tables, populates the tables with the same sample data, and creates the views
for backward compatibility.
5. When you have done this, you may want to shut down Cloudscape and then backup the CMP version of the database to a directory called cmpAgency, using similar
commands to those in step 2. That way, you can easily switch between the two
different schemas. To reinstate either version, just delete the Agency directory and
then copy back either the bmpAgency or cmpAgency using xcopy (Windows) or
cp -r (Unix).
CMP Entity Bean Lifecycle
The lifecycle for CMP Entity beans is substantially the same as BMP Entity beans,
reflected in an almost identical diagram (see Figure 7.3).
This diagram differs from the lifecycle you saw yesterday in that there are no
ejbFindXxx() methods for pooled beans. This is not to say that pooled CMP Entity
beans do not perform finder methods; they do. However, the EJB container generates the
actual code that performs this. There will be a finder method in the bean’s local-home
interface, but not necessarily an equivalent ejbFindXxx() method in the bean itself (and
certainly not in the code written by the bean provider).
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 278
278
Day 7
[pool too small]
/setEntityContext
[pool too large]
/unsetEntityContext
FIGURE 7.3
The
Pooled
javax.ejb.EntityBean
lifecycle for CMP
Entity beans.
/ejbActivate
/ejbCreate
Creating
exit/^ejbLocalObject.new()
/ejbPostCreate
/ejbPassivate
Cached
/ejbRemove
Note
ejbLoad
/“business method”
ejbStore
Where the implementation of the finder methods is will depend on the EJB
container. It does seem likely that many vendors will choose to place the
finder logic in the bean’s concrete implementation. An advantage of this
approach (for the EJB container vendor) is that the rest of the EJB container
need not differentiate between BMP and CMP Entity beans.
Yesterday, you saw the responsibilities of the bean provider for each of these lifecycle
methods for the Job bean when implemented using BMP. The implementation is somewhat simpler when using CMP, as shown next.
As for BMP Entity beans, the setEntityContext() and unsetEntityContext() methods must be implemented to look up any required resources (although the resources are
likely to be different than those needed for BMP; in particular, the JDBC DataSource
should not be needed).
Under BMP, the ejbCreate() method was responsible for persisting the newly created
bean’s state to the persistent data store. Under CMP, the ejbCreate() method does not
need to do this, but does still need to set the bean’s fields to the parameters passed in.
This can include generating a unique primary key value.
09 0672323842 CH07
3/20/02
12:20 PM
Page 279
CMP and EJB QL
279
The BMP version of ejbRemove() was responsible for physically removing the bean’s
state from the data store. The CMP version does not need to do this (and later on, you’ll
see that this method actually has a null implementation).
Nevertheless, sometimes there may be occasions when work needs to be done in the
ejbRemove() method. For example, you could imagine an Entity bean that has some subscribers interested in observing its changing state. One such Entity bean might be a headline news item publicized from Reuters. When this Entity bean is finally removed, it
would notify those subscribers of the event.
On a more mundane level, the bean might want to prevent the delete from occurring if,
for example, some referential integrity constraint would be violated by the bean being
removed. In such cases, you need to know that the ejbRemove() is called before the container actually removes the data.
Next are the ejbLoad() and ejbStore() methods. Under BMP, these methods for the
bean were substantial, because they had to read/write the bean’s state to both the Job
and JobSkill tables. Under CMP, unless there is any derived data to be maintained,
these methods could well have an empty implementation.
Job
The ejbActivate() and ejbPassivate() methods for a CMP Entity bean are pretty
much identical to their implementations for a BMP Entity bean; after all, these methods
have nothing to do with the persistent data store.
If you’ve been mentally (or manually) comparing this section with the previous section
yesterday, you’ll note that yesterday there was discussion on the finder methods. There is
no such discussion here because the finder methods will be generated automatically.
However, as the bean provider, you will need to indicate to the container the semantics of
the finder queries. To do that, you must be familiar with EJB QL. But to understand EJB
QL queries of any complexity, you need first to understand container-managed relationships. CMR is discussed in the next section, followed then by EJB QL.
Container-Managed Relationships
Container-managed relationships (CMR) might possibly sound pretty daunting, and certainly from the EJB container vendor’s perspective, there could be some fairly complex
activity happening behind the scenes. However, from the bean provider’s perspective
(that is, you), they are fairly straightforward and easy to use.
CMRs are defined declaratively through the deployment descriptor, underneath the
relationships element. Therefore, container-managed relationships can only be defined
between Entity beans that reside within the same local relationship scope (EJB specification,
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 280
280
Day 7
section 10.3.2). What this means in practice is that beans that have relationships must be
deployed in the same ejb-jar file. You will be learning more about actually declaring
CMRs later today, in “Configuring a CMP Entity Bean.”
Note
The restriction that CMR can only be defined between EJBs deployed in the
same ejb-jar file could possibly create problems. Some organizations maintain static reference data globally and replicate that data locally. This works
because such data is often updated relatively infrequently.
By its nature, reference data is referenced (!), so one would expect relationships from domain-specific Entity beans up to cross-organizational reference
beans. However, if the reference Entity beans are deployed separately from
the domain-specific Entity beans (as would be likely), no such relationships
can be assigned.
Relationship Types
CMR allows three different types of relationships to be defined between Entity beans:
• One-to-one
• One-to-many
• Many-to-many
The first two relationship types are to be expected, but the last is perhaps more unexpected if you are used to using RDBMS. In relational theory, it is not possible to create a
many-to-many relationship directly; instead, a link (or association) table is required.
Indeed, such a table can be seen directly in the BMP version of the case study database;
the many-to-many link between jobs and skills is captured in the JobSkill table.
However, the EJB specification allows many-to-many links to be defined directly for
Entity beans, an immediate simplification over the RDBMS approach.
Note
Of course, most EJB containers—the J2EE RI included—will persist to RDBMS,
so will require a link table in the physical schema. Indeed, the J2EE RI uses a
link table even for one-to-many associations. You can see this if you look
back to Figure 7.2. For example, the one-to-many link from Job to Location
is captured through the snappily named
JobBean_location_LocationBean_Table table.
09 0672323842 CH07
3/20/02
12:20 PM
Page 281
CMP and EJB QL
281
These relationship types actually refer to the maximum cardinality (also sometimes
called multiplicity) of the related beans in the relationship. That is, saying that there is “a
one-to-many relationship between Location and Job” is shorthand for “the maximum
number of Locations that a Job can be related to is one, and the maximum number of
Jobs that a Location can be related to is many.” There is also the question of minimum
cardinality. In other words, is it necessary for a Job to be related to any Location (or can
it be related to none)? Equally, must a Location have any Job related to it?
The EJB specification answers this question implicitly by always allowing a minimum
cardinality of zero. Hence, “one-to-many” also allows for none-to-many, one-to-none,
one-to-many, and (trivially) none-to-none.
There are sometimes situations when a minimum cardinality of none is not acceptable.
For example, it might be the case that a Job must always relate to a Location. (Actually,
for the case study, this is not enforced except in principle). In these cases, it is up to the
bean to do the appropriate validation. In other words, the Job bean would only define a
create() method that accepted a Location bean reference, and if it provided a
setLocation() accessor method in its interface, it would ensure that the supplied
Location reference was not null.
A related question is, “What happens if a bean is removed?” Suppose that the Job bean
relates to a Location, and the Location bean is deleted. The Job bean will be left with a
null reference. In relational terms, this is sometimes called a cascade null.
Suppose (again) that every Job must always relate to a non-null Location. There are a
number of options:
• The first, somewhat radical, option is to remove the related Job beans—in other
words, perform a cascade delete. CMR supports this directly (it is specified
through the deployment descriptor) and will remove each Job bean in turn.
• Second, the application can prevent the removal of the foreign key Location bean
from occurring. This would be done by implementing an appropriate check in the
ejbRemove() lifecycle method.
• Another alternative would be to reassign every impacted Job bean to some new
Location. Again, the ejbRemove() method would need to do this work.
The second option is probably the most likely, so you should take care to do this type of
minimum cardinality analysis to make sure that you do not unwittingly end up with
beans that have null relationships when the semantics of the problem domain prohibit
this from occurring.
7
09 0672323842 CH07
3/20/02
12:20 PM
282
Page 282
Day 7
Navigability
In addition to specifying multiplicity of the relationships, CMR also allows the navigability of the relationship to be defined. The navigability is either unidirectional or bidirectional.
Navigability is defined by indicating the name of the field that references the related
bean. For example, in a many-to-one relationship between the Job and Location beans,
indicating a field of location for the Job bean means that there is navigability from Job
to Location. There may not necessarily be navigability in the opposite direction; that
would depend on whether the Location bean defines a field called jobs.
Caution
While the case study does not require bi-directional navigability either from
Location to Job or from Skill to Job, it does define navigability nevertheless. Otherwise, the code generated by the J2EE RI 1.3 deployment tools
(somewhat unfortunately) does not compile—not something to inspire confidence!
The term “field” (or more properly, cmr-field) used here indicates the accessor methods
for the virtual fields defined in the CMP superclass and implemented by the EJB container. The next section looks at these methods in more detail.
cmr-fields
By way of example, the location cmr-field for the Job bean has accessor methods of
getLocation() and setLocation(), and the skills cmr-field has the accessor methods getSkills() and setSkills(). The return type of these methods depend on the
multiplicity of the relationship.
The relationship from Job to Location is many-to-one, so the methods that correspond to
the location cmr-field are as follows:
public abstract LocationLocal getLocation();
public abstract void setLocation(LocationLocal location);
09 0672323842 CH07
3/20/02
12:20 PM
Page 283
CMP and EJB QL
Tip
283
The same restriction on naming that applies to cmp-fields also applies to
cmr-fields: the name must not start with a capital letter.
For single-valued cmr-fields, the return type for the getter and the parameter to the setter is the local interface of the related bean (LocationLocal in this case). Yesterday, it
was noted that local interfaces are the cornerstone of container-managed relationships;
this shows why. Remote interfaces cannot be used in CMR.
Note
This isn’t recommended, but there is nothing in principle to prevent an
Entity bean with only a remote interface from having relationships with
other Entity beans. However, the target Entity beans must themselves have a
local interface, and the relationship will be unidirectional. The absence of a
local interface in the source Entity bean prevents the related Entity beans
navigating back to the Entity bean.
The Job bean also has a relationship with the Skill bean, this time many-to-many. Thus,
the skills cmr-field corresponds to the following methods:
public abstract java.util.Collection getSkills();
public abstract void setSkills(java.util.Collection skills);
This is a multi-valued cmr-field because a collection of values is returned, not just a
single value. The collection returned here is a java.util.Collection of references to
the local interface of the related bean (SkillLocal in this case). The EJB specification
also allows for java.util.Sets to be returned. The EJB specification does not currently
allow Lists or Maps to be returned from multi-valued cmr-fields, but does hint that they
may be added in the future.
Note that the fields of a bean are either regular cmp-fields or they are cmr-fields (or
they are just regular instance variables, not managed by the container at all). Put another
way, cmp-fields cannot be defined that have references to other beans as their argument
or return type; such fields must be defined as cmr-fields.
7
09 0672323842 CH07
284
3/20/02
12:20 PM
Page 284
Day 7
Composite Primary Keys and Relationships
The Job Entity bean has a relationship with both the Location bean and the Customer
bean. The getCustomer() method returns the name of a customer as a String, whereas
the getLocation() method returns a reference to a LocationLocal. In other words, the
former returns the name (the primary key) to a bean, and the latter returns the bean
itself. So why the difference?
The reason is that the customer field is part of the primary key for the Job bean, and
appears in JobPK. Every public field in JobPK must have a corresponding field in the bean
itself.
If the JobPK class defined its customer field to be a reference to a CustomerLocal, the
JobPK class could not be guaranteed to be serializable.
This shows up a very subtle area, not highlighted at all in the EJB specification. In the
case study, there is a relationship between Customer and Job, in that Jobs are identified
by Customer. In other words, the primary key of Job contains the primary key of the
Customer that “owns” that Job.
The case study does not define the one-to-many relationship between Customer and Job.
If this had been done, a virtual field and corresponding accessor methods
({get/set}CustomerObj() methods) would need to have been defined. The problem that
would then have arisen, however, is that potentially the name of the customer returned
by getCustomer() may not correspond to the actual customer returned by
getCustomerObj().
In commercial EJB containers, this problem can be resolved by mapping both the
getCustomer() and getCustomerObj() methods to the same physical data in the persistent
data store (the customer column in the Job table). This prevents them from getting out
of step (although even here, the EJB container would need to make the customerObj
field read-only because allowing it to be changed would implicitly change the primary
key of the Job bean).
However, the J2EE RI container does not make the mapping of the Entity beans data to
the physical schema explicit. While it might be possible to modify the implied mapping,
it would be unclear (from an education standpoint) what was being done. For this reason, the case study does not define the Customer/Job relationship. This is why, for example, the customerObj field is derived from the customer field, and is looked up in the
JobBean’s ejbLoad() method.
Not only do the methods corresponding to the cmr-field return and accept only local
and not remote interfaces to beans, they also cannot appear in the remote interface of the
bean. This is not really surprising. After all, the return types and arguments to the methods corresponding to the cmr-field take only local interfaces of remote beans, so the
client of the bean invoking the cmr-field methods must be local. You saw yesterday that
there are very good reasons why remote interfaces are bad news for Entity beans; this is
another reason not to provide a remote interface.
09 0672323842 CH07
3/20/02
12:20 PM
Page 285
CMP and EJB QL
285
The other reason that cmr-field methods cannot appear in the remote
interface is to prevent untoward network traffic. The performance cost of
transporting large collections of references across the wire (even assuming
those references were serializable) would be overwhelming.
Note
Table 7.1 compares the use of cmp-fields and cmr-fields in interfaces.
TABLE 7.1
cmp-fields and cmr-fields and Interfaces
Feature
cmp-field
cmr-field
Can appear in local
interface
Yes
Yes
Can appear in remote
interface
Yes
Not recommended though;
Entity beans should be
accessed via remote clients.
No
Can accept as parameters
and return local references
to beans
No
Yes
Can accept as parameters
and return remote references
to beans
cmp-fields
deal only with
primitives (or serializable
objects).
No
deal only with
primitives (or serializable
objects).
cmp-fields
No
Container managed relationships
are defined only through local
interfaces of beans.
The EJB specification also requires that the Collection returned by a cmr-field method
is only used in the same transaction context. You will be learning more about transactions
tomorrow, but for now, just consider the following scenario. A Session bean could invoke
a multi-valued cmr-field’s getter method and receive back a Collection. It could then
hold onto this Collection for a few seconds, minutes, days, or months. It might also
remove and add elements to this Collection. If the client then calls the setter method for
the bean, there are no guarantees that either the Collection hasn’t been changed by
some other client of the entity bean (the so-called lost update problem) or that the elements in the originally returned collection are still valid (the repeatable read problem).
The EJB specification’s insistence that collections are only manipulated within a transaction solves these problems, primarily because transactions both prevent the items in the
Collection from being removed, and ensure that new items will remain valid.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 286
286
Day 7
Manipulating Relationships
In the context of an RDBMS, relationships between tables can be manipulated in several
ways. Consider, for example, the case study (with the schema used in Day 6, “Entity
EJBs”). The Job and Skill tables are in a many-to-many relationship, resolved through
the JobSkill link table, and the Job and Location table have a many-to-one relationship, implemented through the location column acting as a foreign key in the Job table.
To change a many-to-many relationship means adding or removing entries from the
appropriate link table. In the example, this means adding or removing (job, skill)
tuples from the JobSkill table.
To change a many-to-one relationship means changing the value of the foreign key column. In the example, this means changing the value of the location column in the Job
table.
Manipulating relationships between Entity beans is somewhat different. As you have
seen, the setter method for a multi-valued cmr-field (such as setSkills()) takes an
entire Collection of items. And for many-to-one relationships (with the appropriate navigability), the relationship can be modified from the “parent” end, just as much as from
the “child” end.
Moreover, for multi-valued cmr-fields, the collection of beans referenced by the relationship can be modified just by using the usual add() or remove() methods of the
java.util.Collection interface.
The EJB specification lays out in some detail the semantics of various actions that
impact relationships between Entity beans. Many of these are straightforward and act as
expected, but a few deserve special comment.
Figure 7.4 shows an example configuration of Location and Job beans, and indicates the
relationships before and after executing:
loc1.getJobs().add(job21)
The relationships indicated {new} are created as a result of this action, while the relationships indicated {destroyed} disappear. (This rather elegant notation is an enhancement
to UML, described by deSouza and Wills’ Catalysis Method.)
Initially, loc1 is associated with job11 through job1n, and similarly, loc2 is associated with
job21 through job2n. You can see that as a result of the action, the job21 bean is added to
the collection of jobs associated with the loc1 location. However, perhaps less obviously,
the job21 bean is also removed from the collection of jobs associated with the loc2 location.
This is because there is a one-to-many relationship between Location and Job, so the job21
bean cannot have a relationship with both loc1 and loc2 at the same time.
09 0672323842 CH07
3/20/02
12:20 PM
Page 287
CMP and EJB QL
287
(new)
FIGURE 7.4
job1.1
Before and after object
diagram for
job2.1
{destroyed}
loc1.getJobs().add
(job21).
loc1
job1.2
job2.2
job1.n
job2.n
loc2
Note that the collection of jobs associated with loc1 changes, even though the
setJobs() method of Location is not called! At least, these are the semantics laid out by
the EJB specification, but it would be wise to check that your own EJB container correctly implements this. (The J2EE RI server does implement this correctly.)
The java.util.Collection interface defines the addAll() method as well as the add()
method, and this is also supported for CMR collections. This takes a collection of elements rather than a single element. For one-to-many associations, the addAll() has the
same semantics as add() in that any child elements (the Job bean in the example) that
are added to some collection will also be removed from the collection where they
resided.
The diagram in Figure 7.4 holds good for either unidirectional relationships with navigability from Location to Job, and for bi-directional relationships.
Figure 7.5 shows the “opposite” case of removing a bean from a collection, having executed
loc1.getJobs().remove(job1n)
FIGURE 7.5
job1.1
job2.1
job1.2
job2.2
job1.n
job2.n
Before and after object
diagram for
loc1.getJobs().
loc1
loc2
remove(job1n).
{destroyed}
7
09 0672323842 CH07
288
3/20/02
12:20 PM
Page 288
Day 7
Initially, loc1 is associated with job11 through job1n, and similarly, loc2 is associated
with job21 through job2n. As you can see, after executing this action, the job1n bean is
no longer associated with any Location bean. Earlier today, it was noted that the EJB
specification always allows a minimum multiplicity of zero, and this is what has
occurred here. Suppose though that every Job should always be related to a Location—
that is, a minimum multiplicity of one. Given that the job’s location is being set to null
indirectly (by calling remove() on the returned Collection from the getJobs()
method), there is no easy way to enforce this business rule.
The only available solution is to not make the getJobs() method available in the interface of the bean. You could provide another method, perhaps named getJobsCopy(), that
returns a copy of the Collection and make it clear that adding or removing beans to this
Collection will not influence the relationship itself. Even better, the Collection could
be made immutable. The following is a possible implementation for this method (assuming java.util.* is imported):
public Collection getJobsCopy() {
List jobs = new ArrayList();
for(Iterator iter = getJobs().iterator(); iter.hasNext(); ) {
jobs.add(iter.next());
}
return Collections.unmodifiableList(jobs);
}
Incidentally, another way of removing elements from the collection returned by a getter
method is to do so using an Iterator. Indeed, changing the contents of a Collection,
either directly by using remove() or indirectly, such as by the use of add() as described
in figure 7.4, will invalidate any Iterators instantiated from that Collection. In any
case, the following would disassociate the loc1 location from all of its jobs:
for(Iterator iter = loc1.getJobs().iterator(); iter.hasNext(); ) {
iter.next();
iter.remove();
}
The diagram in Figure 7.5 again holds good for either unidirectional relationships with
navigability from Location to Job and for bi-directional relationships.
The case study does not define any one-to-one relationships between beans, so imagine
that there is an Employee bean that has a one-to-one relationship with Job. This could
perhaps represent the Employee of the Advertiser who originally placed the Job advert.
In any case, Figure 7.6 shows the object diagram having executed
job1.setEmployee(job2.getEmployee())
09 0672323842 CH07
3/20/02
12:20 PM
Page 289
CMP and EJB QL
FIGURE 7.6
job1
{destroyed}
emp1
289
emp2
{destroyed}
job2
Before and after object
diagram for
job1.setEmployee(
job2.getEmployee() ).
{new}
Initially, job1 has a relationship with emp1, and job2 has a relationship with emp2. After
the action, emp1 has no relationship with any Job, job2 has no relationship with any
Employee, and job1 has a relationship with emp2.
This seems quite straightforward, but again, a minimum multiplicity of zero is needed. If
either every Employee must always relate to a Job, or if every Job must always relate to
an Employee, there is no method where this application validation can be performed. The
Job bean’s setEmployee() method might look like a good candidate, until you realize
that this method is abstract and is generated entirely by the beans’ subclass.
The solution, again, must be to not expose the setter method in the interface of the bean.
Instead, a method, such as setEmployeeField(), could be provided. Its implementation,
for the case where every Job must relate to an Employee (though not every Employee
need have a relationship with a Job), might be as follows:
public void setEmployeeField(EmployeeLocal newEmployee) {
if (newEmployee.getJob() != null) {
throw new IllegalArgumentException(
➥”New employee must not already be related to a job”);
}
setEmployee( newEmployee );
}
As you can see, this implementation requires that the supplied employee is not related to
a job. (If it were, that job would, in turn, need to be assigned a new employee not related
to any job, and so on.)
Note
Clearly, this technique is good wherever some application-level validation is
required. For example, if a bean had a cmp-field called phoneNumber, the
format of the supplied number could be checked.
The diagram in Figure 7.6 holds good for unidirectional relationships with navigability
from Job to Employee and for bi-directional relationships.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 290
290
Day 7
Figure 7.7 returns to the relationship between the Location and Job beans, this time
showing the object diagrams having executed
loc1.setJobs(loc2.getJobs())
FIGURE 7.7
{new}
Before and after object
diagram for
job1.1
job2.1
{destroyed}
loc1.setJobs(loc2.
{destroyed}
getJobs()).
loc1
{destroyed} job1.2
{destroyed}
job1.n
job2.2
job2.n
{destroyed}
loc2
{destroyed}
{new}
{new}
Again, initially loc1 is associated with job11 through job1n, and similarly, loc2 is associated with job21 through job2n. After the action has completed, the Collection of jobs
previously associated to loc2 are associated to loc1. The loc2 bean itself, and the
Collection of jobs previously associated with loc1, no longer have any associations.
The discussion for the diagram in Figure 7.7 follows similar lines to that for the previous
diagram in Figure 7.6. Specifically, handling minimum cardinality of one for either Job
or Location will require the setJobs() method to be removed from the interface of the
bean. Instead, a method, such as setJobsField(), should be exposed that will do the
validation and then delegate to setJobs().
It’s also worth remarking that behind the scenes, the EJB container is having to perform
some significant updates to the persistent data store. Assuming an RDBMS physical
schema of Location and Job tables with a foreign key in the Job table, the foreign key
for job11 through job1n would need to be set to null, and the foreign key for job21
through job2n updated to be loc1.
Note
You saw earlier today that the actual physical schema used by J2EE RI actually has a separate link table called JobBean_location_LocationBean_Table
that holds the foreign key. For the J2EE RI to implement this action, it would
need to delete all the rows in this table for loc1 and loc2, and then reinsert new rows corresponding to the (loc1, job21)… (loc1, job2n)
tuples.
09 0672323842 CH07
3/20/02
12:20 PM
Page 291
CMP and EJB QL
291
If you were to design a bean with multi-valued relationships without using CMR, you
might well have provided methods such as addSkill() and removeSkill(). As you now
appreciate, CMR itself does not require or provide such methods, relying instead on a
single setter method for the cmr-field.
Of course, there is nothing to prevent providing methods, such as addSkill() and
removeSkill(), in the bean itself. Indeed, as you have seen, there are good reasons why
the setSkills() method might not appear in the local interface to the bean; providing
just addSkill() and removeSkill() in the interface would provide an (arguably) more
natural interface to the bean while also allowing the bean to perform any applicationspecific validation.
Finally in this section, note that it is illegal to call the setter method of a multi-valued
cmr-field with null; a java.lang.IllegalArgumentException will be thrown by the
EJB container if this is attempted. In the example, to indicate that a job has no skills
associated with it, an empty collection (for example,
java.util.Collection.EMPTY_LIST) must be passed as the argument to setSkills().
EJB QL
By now, you should have a good idea as to how a bean’s CMP fields and CMR fields are
managed and persisted by the EJB container. However, there is still one area that you
covered yesterday when you were learning about BMP Entity beans that has not yet been
touched on today, and that is how to specify finder methods. For this, you need to know
something about the EJB Query Language, or EJB QL.
EJB QL was introduced in the EJB 2.0 specification to make beans more portable across
EJB containers. Previously, each EJB container vendor created their own proprietary
mechanism for specifying the semantics of finder methods. This is no longer the case.
As a language, EJB QL is based on ANSI SQL, which you have seen embedded within
the case study code (for example, on Day 5, “Session EJBs” and Day 6, “Entity EJBs”).
EJB QL also bears some similarity with the Object Constraint Language (OCL), part of
UML. Familiarity with ANSI SQL (and ideally OCL too) will see you well on the way to
picking up EJB QL.
Select Methods
The EJB 2.0 specification also introduces another concept that also uses EJB QL queries,
namely that of select methods. These are like finder methods in that their purpose is to
return data from the persistent data store, but there are also some important differences.
Table 7.2 is an expanded version of the comparison that appears in the EJB specification.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 292
292
Day 7
TABLE 7.2
Finder and Select Methods Compared
Feature
Finder Method
Select Method
Can be defined for CMP
Entity beans
Yes
Yes
Can be defined for BMP
Entity beans
Yes
No
Semantics specified using
EJB QL query, implementation
generated by container
Yes
Yes
Appears in local-home (home)
interfaces
Yes
This is the way in which
formal arguments are specified.
No
Abstract method signature in
bean class
No
Yes
This is the way in which the
formal arguments are specified.
Returns local (remote)
references to bean
Yes
Local references are returned if
specified on local-home interface,
remote references if specified on
home interface.
Yes
By default, local references
are returned, but remote
references can be returned if
indicated in the deployment
descriptor (the result-typemapping element).
Returns any cmp-field or
No
Yes
A different syntax of EJB QL
query is used.
cmr-field
Returns java.util.Collections Yes
of references
Yes
Returns java.util.Sets of
references
No
Duplicates can be eliminated
using the distinct keyword in
the EJB QL query.
Yes
Returns java.util.
Enumerations of references
Yes
This is to support legacy EJB 1.x
beans; do not use.
No
Called on pooled beans
Yes
Yes
The EJB container selects an
If being called from a home
unused bean to perform the query. method.
09 0672323842 CH07
3/20/02
12:20 PM
Page 293
CMP and EJB QL
TABLE 7.2
293
Continued
Feature
Finder Method
Select Method
Called on activated beans
No
Yes
If being called from a business
method, this allows the underlying query to be logically
scoped (for example, based on
the primary key), if needed.
Own transaction context can
be specified
Yes
No
Because the select method
does not appear in the interfaces, no transaction context
can be defined. Instead, the
context of the calling method
is used.
You’ll be learning how to write select methods later, as part of specifying and implementing CMP beans.
Syntax and Examples
The EJB Specification formally defines the syntax of EJB QL queries using Bacchus
Normal Form (BNF). This can be somewhat heavy going to wade through, but is comprehensive. This section introduces EJB QL using BNF, with annotations and examples
along the way.
An EJB QL query is defined as follows:
EJB QL ::= select_clause from_clause [where_clause]
This says that a query consists of a select_clause and a from_clause, and optionally a
where_clause. Immediately, you can see the similarity with ANSI SQL.
In ANSI SQL, you may recall that the columns listed in the SELECT clause relate to the
tables identified in the FROM clause. So in exploring EJB QL, it makes sense to look at
the from_clause before the select_clause; after all, both are needed for any wellformed EJB QL query.
from_clause
The from_clause is defined as follows:
from_clause ::= FROM identification_variable_declaration
➥[, identification_variable_declaration]*
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 294
294
Day 7
This says that in an EJB QL query, the from_clause consists of one or more identification_variable_declaration clauses. This corresponds to ANSI SQL where there are
one or more tables/views listed in a SQL FROM clause. The
identification_variable_declaration clause is then defined as follows:
identification_variable_declaration ::=
➥{range_variable_declaration | collection_member_declaration}
➥[AS ] identification_variable
where
range_variable_declaration ::= abstract_schema_name
and
collection_member_declaration ::= IN (collection_valued_path_expression)
collection_valued_path_expression ::=
➥identification_variable.
➥[single_valued_cmr_field.]*
➥collection_valued_cmr_field
In other words, each item in the from_clause either is just an abstract_schema_name, or
is an expression of the form IN (o.abc.def.xyz). The abstract_schema_name is probably the easier expression to understand. Each CMP Entity bean has a corresponding
abstract_schema_name, ultimately declared in the deployment descriptor. So in ANSI
SQL terms, this is very similar to just listing the “table” that corresponds to the bean.
The IN (o.abc.def.xyz) expression is similar to an OCL expression. Here o refers is
the identication_variable assigned by the previous AS phrase. In other words, it corresponds to some abstract_schema_name (that is, bean) also in the from_clause. The abc
and def are single_value_cmr_fields. In other words, these are fields of the o bean
that return a single element. The abc is a field of o, returning a reference to a bean, and
def is a field of this reference that has a field xyz. This xyz field, in turn, returns a
Collection or List of some other data (a bean or otherwise).
That’s pretty heavy, so some examples from the case study may clarify things. Assuming
that the Job bean has an abstract schema name called Job, that the Customer bean has an
abstract schema name called Customer, and so on.
FROM Job AS j
sets up an identification_variable called j that refers to the Job schema. The comparison with ANSI SQL is obvious; the syntax is the same.
FROM Job AS j, IN (j.skills) AS s
sets up the j identification_variable as before and also an
identification_variable called s that refers to each of the skills related to the Job
bean in turn.
09 0672323842 CH07
3/20/02
12:20 PM
Page 295
CMP and EJB QL
295
Comparing this to ANSI SQL, this most likely would correspond to
FROM
INNER JOIN
ON
AND
Job AS j
JobSkill AS s
s.customer = j.customer
s.ref = j.ref
or if you prefer the old-fashioned way:
FROM Job AS j, JobSkill AS s
WHERE s.customer = j.customer
AND s.ref = j.ref
The following is another example:
FROM Job AS j, IN (j.location.jobs) AS k
sets up the j identification_variable as before and also an identification_variable called k that refers to all of the jobs that have the same location as the original job.
To see this, note that j.location returns a reference to the Location bean for the Job,
and then that j.location.jobs returns the Collection of Jobs for that Location. Of
course, this Collection will include the original job, but it will include others as well.
Comparing this to ANSI SQL, you can see that this is a self-join:
FROM Job AS j
INNER JOIN Job AS k
ON j.location = k.location
or in the old money:
FROM Job AS j, Job AS k
WHERE j.location = k.location
select_clause
The select_clause is defined as follows:
select_clause ::= SELECT [DISTINCT ] {single_valued_path_expression |
➥OBJECT (identification_variable)}
The easy case is the SELECT OBJECT(o) style of the select_clause, where o is an
identication_variable defined by the from_clause. (You see now why the
from_clause was presented first.) This returns all the data from the data store required to
instantiate a bean. In ANSI SQL terms, you might think of it as an intelligent SELECT *
FROM ....
All finder methods must use the SELECT OBJECT(o) style, where the objects returned are
of the schema associated with the bean for which the finder is being specified. The other
style of the select_clause, using a single_valued_path_expression is for use only by
select methods that you were introduced to briefly earlier. (More on these to follow.)
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 296
296
Day 7
The following are some examples from the case study.
SELECT OBJECT(j)
FROM Job as j
will return all jobs. If the JobLocalHome interface defined a finder method called
findAll(), this would be the corresponding EJB QL query.
The next example
SELECT DISTINCT OBJECT(s)
FROM Job as j, IN (j.skills) as s
will return back all skills used by any job. Because some skills will be required by more
than one job, the DISTINCT keyword is used to eliminate duplicates. This EJB QL query
might perhaps be associated with a finder method called findAllRequiredSkills() on
the SkillLocalHome interface.
The other style of the select_clause uses a single_valued_path_expression. This is
defined as follows:
single_valued_path_expression ::=
➥{single_valued_navigation | identification_variable}
➥.cmp_field | single_valued_navigation
where a single_valued_navigation is
single_valued_navigation ::= identification_variable.[single_valued_cmr_field.]*
➥single_valued_cmr_field
Taking these definitions together, a single_valued_path_expression is effectively just
a chain of (none or many) single_valued_cmr_fields (cmr-fields returning a reference to a single bean, and not a collection), eventually finishing with a cmp-field.
In the following examples
SELECT DISTINCT j.location.name
FROM Job as j
is the cmr-field of the Job schema (identified by j), and name is the cmpof the bean referenced by the cmr-field (a Location bean, obviously).
location
field
This returns the names of the locations where there are jobs. Comparing this to ANSI
SQL, you can see that EJB QL is actually simpler (because of its use of the OCL-like
path expressions to navigate between beans):
SELECT DISTINCT l.name
FROM Job as j INNER JOIN Location as l ON j.location = l.location
However, the following is not allowed:
SELECT DISTINCT j.skills.name
FROM Job as j
09 0672323842 CH07
3/20/02
12:20 PM
Page 297
CMP and EJB QL
297
This is because the skills cmr-field of Job returns a collection of skills, not a single
skill. The correct way to phrase this query is as follows:
SELECT DISTINCT s.name
FROM Job as j, IN (j.skills) AS s
You might like to think of the s identification variable as an iterator over the collection
of skills returned by the skills cmr-field.
Note that the select_clause in EJB QL can only ever return a single item of information, so the following also is not allowed:
SELECT DISTINCT j.location.name, j.location.description
FROM Job as j
where_clause
The where_clause is optional in an EJB QL query, but will be present in the majority of
cases. It is defined in BNF as follows:
where_clause ::= WHERE conditional_expression
conditional_expression ::=
➥conditional_term | conditional_expression OR conditional_term
conditional_term ::=
➥conditional_factor | conditional_term AND conditional_factor
conditional_factor ::=
➥[NOT ] conditional_test
This just says that clauses can be combined using the usual AND, OR, and NOT. Much of
the rest of the formal BNF definitions for the where_clause also makes somewhat heavy
work of some fairly straightforward concepts, so to paraphrase,
•
conditional_tests
can involve =, >, <, >=, <=, and <> operators. These apply variously to numbers and datetimes (all operators), and strings, Booleans, and Entity
beans (the = and <> operators). You will recall that Entity beans are considered
identical if their primary keys are equal.
• Comparisons can involve input parameters, where these correspond to the arguments of the finder or select method. More on this topic shortly.
• Arithmetic expressions can use the BETWEEN...AND operator, just as in ANSI SQL.
• String expressions can be compared against lists using the IN operator and against
patterns using the LIKE operator. These also exist within ANSI SQL. Unlike Java,
string literals should appear in single quotes.
• The IS NOT NULL operator exists to determine if an object is null. Again, this syntax is borrowed from ANSI SQL.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 298
298
Day 7
Note
ANSI SQL supports the concept of nullable primitives (ints and so on),
whereas Java and EJB QL do not. However, nullable primitives can be simulated by using wrapper classes as cmp-fields and using these in EJB QL
expressions.
There are some more operators to EJB QL and some built-in functions, but first, some
examples using these operators are in order.
SELECT OBJECT(c)
FROM Customer AS c
WHERE c.name LIKE “J%”
This will find all customers whose name begins with the letter J. Note that the ANSI
SQL wildcards (% to match none or many characters, _ to match precisely one character)
are used.
The following
SELECT OBJECT(j)
FROM Jobs AS j
WHERE j.location IS NULL
will find all jobs where the location has not been specified.
SELECT l.description
FROM Location AS l
WHERE l.name IN (“London”, “Washington”)
returns the descriptions of the locations named London and Washington.
The where_clause can also include input parameters. These parameters correspond to
the arguments of the finder or the select method, as defined in the local-home interface
or bean, respectively.
For example, the Job bean declares the following finder method in the JobLocalHome
interface:
Collection findByCustomer(String customer);
The EJB QL query for this finder method is as follows:
SELECT OBJECT(j)
FROM Job AS j
WHERE j.customer = ?1
?1 acts as a placeholder, with the 1 indicating that the first argument of the finder
method (the customer string) be implicitly bound to this input parameter. Unlike
JDBC SQL strings, the number is required because the binding is implicit, not explicit.
09 0672323842 CH07
3/20/02
12:20 PM
Page 299
CMP and EJB QL
299
It is also needed in the cases where a single argument is used more than once in the
query. For example, consider the following finder method:
Collection findLocationsNamedOrNamedShorterThan(String name);
This might have an EJB QL query of
SELECT OBJECT(l)
FROM Location AS l
WHERE l.name = ?1
OR LENGTH(l.name) < LENGTH(?1)
This example uses the built-in function LENGTH that returns the length of a String. In any
case, this rather peculiar finder method will find those locations that have the exact
name, and will also return any name whose length is strictly shorter than the supplied
name. You can see that the ?1 placeholder appears more than once because the name
argument needs to be bound to the query in two places.
EJB QL defines just a few more built-in functions. The functions that return a string are
CONCAT and SUBSTRING. The functions that return a number are LENGTH, ABS, SQRT, and
LOCATE. This last is effectively the same as String.indexOf(String str, int
fromIndex).
EJB QL defines two final operators—IS [NOT] EMPTY and [NOT] MEMBER OF. Neither of
these have any direct equivalents in ANSI SQL, but both do have equivalents in OCL.
The IS [NOT] EMPTY operator can is similar to the isEmpty operator of OCL. It can be
used to determine whether a collection returned by a cmr-field is empty. For example,
SELECT OBJECT(s)
FROM Skill AS s
WHERE s.jobs IS EMPTY
will return all those skills that are not marked as required by any job. This might be the
query for a finder method on the SkillLocalHome interface, called something like
findNotNeededSkills().
In fact, this type of query can be expressed in ANSI SQL, though it does require a subquery:
SELECT s.*
FROM Skill AS s
WHERE NOT EXISTS
(
SELECT *
FROM JobSkill AS j
WHERE s.skill = j.skill )
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 300
300
Day 7
The [NOT] MEMBER OF operator is similar to the include operator of OCL. Consider the
following finder method on the JobLocalHome interface:
Collection findJobsRequiringSkill(SkillLocal skill);
The EJB QL query for this would be
SELECT OBJECT(j)
FROM Job AS j
WHERE ?1 MEMBER OF j.skills
Again, this can be expressed in ANSI SQL, but only using a subquery:
SELECT j.*
FROM Job AS j
WHERE EXISTS
(
SELECT *
FROM JobSkill AS
WHERE j.customer
AND
j.ref
AND
s.skill
s
= s.customer
= s.ref
= ?1
)
Further Notes
You may have heard of the “OO/relational impedance mismatch.” This is that to deal
with objects, each must be instantiated and then a message sent to it. On the other hand,
relational theory deals with sets of elements sharing some common attribute; to identify
these elements without instantiating them effectively breaks encapsulation.
EJB QL does a pretty reasonable job of reconciling these concerns. By allowing queries
to be expressed in a set-oriented syntax, the EJB container can easily map these to ANSI
SQL when the persistent data store is an RDBMS. On the other hand, the generated
implementations of finder methods return only objects or Collections of objects.
Nevertheless, EJB QL does have some limitations. For example, when constructing an
EJB QL query, only declared relationships between beans can be followed. It is not possible to join arbitrary fields together (as it is in ANSI SQL). For example, those
Customers who are also Applicants could not be identified using a condition such as
Applicant.name = Customer.name.
There are a number of other cases where EJB QL is not (yet) as powerful as ANSI SQL.
For example, EJB QL does not support grouping and aggregating, ordering, subqueries,
and unions. Expect these features to be added as EJB QL matures. Also, even though
EJB QL does not directly support subqueries, one might not be needed anyway thanks to
the IS [NOT] EMPTY and [NOT] MEMBER OF operators.
09 0672323842 CH07
3/20/02
12:20 PM
Page 301
CMP and EJB QL
301
Specifying a CMP Entity Bean
Specifying a CMP Entity bean is identical to specifying a BMP Entity bean; it consists
of defining the local-home interface and the local interface. This makes sense; after all,
to the user of the bean, it should not matter whether the bean is implemented internally
using CMP or BMP.
The Local-Home Interface
For completeness, Listing 7.2 is the local-home interface of the Job bean.
LISTING 7.2
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
JobLocalHome Interface
package data;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
public interface JobLocalHome extends EJBLocalHome
{
JobLocal create (String ref, String customer) throws CreateException;
JobLocal findByPrimaryKey(JobPK key) throws FinderException;
Collection findByCustomer(String customer) throws FinderException;
Collection findByLocation(String location) throws FinderException;
void deleteByCustomer(String customer);
}
The Local Interface
Listing 7.3 shows the local interface for the Job bean; it, too, is unchanged from the
BMP version.
LISTING 7.3
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
JobLocal Interface
package data;
import java.rmi.*;
import javax.ejb.*;
import java.util.*;
public interface JobLocal extends EJBLocalObject
{
String getRef();
String getCustomer();
CustomerLocal getCustomerObj(); // derived
void setDescription(String description);
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 302
302
Day 7
LISTING 7.3
Continued
14:
15:
16:
17:
18:
19:
20:
21: }
String getDescription();
void setLocation(LocationLocal location);
LocationLocal getLocation();
Collection getSkills();
void setSkills(Collection skills);
Implementing a CMP Entity Bean
Just as for BMP Entity beans, implementing a CMP Entity bean involves providing an
implementation for the methods of the javax.ejb.EntityBean, corresponding methods
for each method in the home interface, and a method for each method in the remote
interface.
Implementing javax.ejb.EntityBean
Under BMP, the setEntityContext() method was used to look up various bean home
interfaces from JNDI, and the JDBC DataSource called java:comp/env/jdbc/Agency
was also obtained. Because most of these relationships are now managed by the container, only a couple of home interfaces now need to be obtained, and there is no requirement to look up the DataSource. Listing 7.4 shows this.
LISTING 7.4
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
The JobBean’s setEntityContext() and unsetEntityContext() Methods
package data;
import javax.ejb.*;
import javax.naming.*;
// imports omitted
public abstract class JobBean implements EntityBean {
private EntityContext ctx;
public void setEntityContext(EntityContext ctx) {
this.ctx = ctx;
InitialContext ic = null;
try {
ic = new InitialContext();
customerHome = (CustomerLocalHome)
➥ic.lookup(“java:comp/env/ejb/CustomerLocal”);
jobHome = (JobLocalHome)
➥ic.lookup(“java:comp/env/ejb/JobLocal”);
09 0672323842 CH07
3/20/02
12:20 PM
Page 303
CMP and EJB QL
LISTING 7.4
303
Continued
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32: }
}
catch (NamingException ex) {
error(“Error looking up depended EJB or resource”, ex);
return;
}
}
public void unsetEntityContext() {
this.ctx = null;
customerHome = null;
jobHome = null;
}
// code omitted
The lookup of the CustomerHome home interface is required because the relationship
from Customer to Job is maintained by the bean.
The lookup of the JobHome home interface is required only because the
ctx.getLocalHome() method returns NULL for the J2EE RI 1.3. This would appear to be
a bug. The ejbHomeDeleteByCustomer() home method actually uses the bean’s own
home interface.
As noted previously, under CMP, the ejbLoad() and ejbStore() methods often have
empty implementations. If there is derived data to be maintained, it should be managed
here. Indeed, the JobBean class does need to do this, as shown in Listing 7.5.
LISTING 7.5
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
The JobBean’s ejbLoad() and ejbStore() Methods
package data;
import javax.ejb.*;
import javax.naming.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public void ejbLoad() {
JobPK key = (JobPK)ctx.getPrimaryKey();
try {
this.customerObj =
➥customerHome.findByPrimaryKey(getCustomer());
}
catch (FinderException e) {
error(“Error in ejbLoad (invalid customer) for “ + key, e);
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 304
304
Day 7
LISTING 7.5
Continued
17:
18:
19:
20:
21:
22:
23: }
}
}
public void ejbStore() { }
// code omitted
The ejbLoad() method is called after the bean’s state has been populated, so the bean’s
state can be read through the accessor methods, if needed.
The findByPrimaryKey() method call on line 13 populates the customerObj field, using
the value of getCustomer() accessor method. It’s worth appreciating that getCustomer()
returns just a String. In other words, this is the name (actually, the primary key) of a customer. To save business methods having to continually look up the actual customer that
corresponds to this customer name, the ejbLoad() method does it once.
You can see that the ejbStore() method is trivial—there is nothing to do.
The ejbActivate() and ejbPassivate() methods have nothing to do with data stores, so
their implementation is unchanged from the BMP version. This is shown in Listing 7.6.
LISTING 7.6
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
The JobBean’s ejbActivate() and ejbPassivate() Methods
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public void ejbPassivate() {
setRef(null);
setCustomer(null);
customerObj = null;
09 0672323842 CH07
3/20/02
12:20 PM
Page 305
CMP and EJB QL
LISTING 7.6
305
Continued
12:
13:
14:
15:
16:
17:
18:
19: }
setDescription(null);
setLocation(null);
}
public void ejbActivate() { }
// code omitted
Implementing the Local-Home Interface Methods
The methods of the local-home interface are implemented partly in code and partly
through the provision of an appropriate EJB QL query. This section shows the queries
that correspond to the finder methods in the home interface; the next section (configuring
a CMP Entity bean) shows how to take those EJB QL strings and put them into the correct part of the deployment descriptor.
Create and Remove Methods
Listing 7.7 shows the ejbCreate() method for Job bean under CMP.
LISTING 7.7
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
The JobBean’s ejbCreate() Method
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public JobPK ejbCreate(String ref, String customer)
➥throws CreateException {
// validate customer login is valid.
try {
customerObj = customerHome.findByPrimaryKey(customer);
} catch (FinderException ex) {
error(“Invalid customer.”, ex);
}
JobPK key = new JobPK(ref,customerObj.getLogin());
// for BMP, there was a workaround here,
➥namely to call ejbFindByPrimaryKey
// under CMP, cannot call since doesn’t exist.
// instead, use jobHome interface ...
try {
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 306
306
Day 7
LISTING 7.7
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
Continued
jobHome.findByPrimaryKey(key);
throw new CreateException(“Duplicate job name: “+key);
}
catch (FinderException ex) {}
setRef(ref);
setCustomer(customer);
setDescription(null);
setLocation(null);
return null;
}
// code omitted
34: }
Note the use of accessor methods (the “setter” methods) to save the bean’s state. This
contrasts with the BMP equivalent where the fields were written to directly (for example,
this.ref = ref).
The implementation of this method would have been even shorter if the
findByPrimaryKey() call checking for duplicates had been omitted. Indeed, if an
RDBMS is being used as the persistent data store (as it is for the case study), there is
likely to be a unique index on the primary key on the appropriate table, meaning that any
attempt to INSERT a duplicate would be detected.
Caution
Strictly, the appropriate exception to throw here is a
DuplicateKeyException, not a CreateException. However, the EJB
specification does not mandate this. Moreover, the specification even allows
that the EJB container can defer any interaction with the persistent data
store until the end of the transaction, and is not clear in this circumstance
what type of exception should be raised.
As the EJB specification continues to mature, it will more fully define expected exceptions in different situations. Until then, beware that—regardless of
the hype—moving from one EJB container to another may well throw up
differences that will necessitate changes in your application.
The ejbCreate() method is called first, and then the container’s concrete implementation persists the bean’s state, and then the bean’s ejbPostCreate() method is called.
09 0672323842 CH07
3/20/02
12:20 PM
Page 307
CMP and EJB QL
307
Under CMP, the bean should return null from the ejbCreate() method. This compares
to returning the actual primary key under BMP. Put another way, it doesn’t matter what
your method returns, it will be ignored. The reason for this is that the EJB container can
access the information that constitutes the primary key anyway, by virtue of the cmpfields.
Note
The technical reason that the EJB container requires that CMP Entity beans
return null is so that EJB container vendors can implement CMP by effectively subclassing the CMP bean and creating a BMP Entity bean (so far as the
rest of the EJB container is concerned). Indeed, if you look at the generated
code, this is precisely what the J2EE RI container does:
package data;
public final class JobBean_PM extends JobBean implements
com.sun.ejb.PersistentInstance {
public data.JobPK ejbCreate(java.lang.String param0,
java.lang.String param1) throws javax.ejb.CreateException {
com.sun.ejb.Partition partition =
com.sun.ejb.PersistenceUtils.getPartition(this);
partition.beforeEjbCreate(this);
super.ejbCreate(param0, param1);
return (data.JobPK) partition.afterEjbCreate(this);
}
// code omitted
}
You can see the call to super.ejbCreate(). The return type is ignored, but
the subclass’ ejbCreate() does return a primary key to the rest of the EJB
container.
The ejbRemove()method for the Job bean is shown in Listing 7.8.
LISTING 7.8
1:
2:
3:
4:
5:
6:
7:
8:
The JobBean’s ejbRemove() Methods
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public void ejbRemove() { }
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 308
308
Day 7
LISTING 7.8
9:
10:
11: }
Continued
// code omitted
As you can see, the implementation of ejbRemove() is trivial—there is nothing to do.
Nevertheless, an implementation is required.
Caution
The BMP version of ejbRemove() for Job bean reset all the fields to null.
Strictly speaking, there was no direct requirement for doing this, because
when the bean instance is next used from the pool, its ejbLoad() will
(should) populate all fields.
When implementing CMP Entity beans, you absolutely must not reset the
fields to null. Doing so will cause the EJB container to throw an exception,
because the bean’s state is required so that the container can remove the
correct data from the persistent data store.
Finder Methods
The implementation of the finder methods is by formulating appropriate EJB QL queries.
The JobLocalHome interface defines three finder methods—findByPrimaryKey(),
findByCustomer(), and findByLocation().
The first bit of good news is that there is no need to define an EJB QL query for the
findByPrimaryKey() method at all. You will recall that the primkey-class element is
used in the deployment descriptor to indicate to the EJB container the class (either custom or pre-existing) that represents the primary key of the Entity bean. When there is a
custom primary key class, the EJB container can use the structure of that class to figure
out how to implement this method.
If there is no custom primary key class, an additional piece of information is required in
the deployment descriptor—namely, the primkey-field element. This nominates the
(single) cmp-field that represents the primary key for the bean.
If you are using a custom primary key class (such as JobPK), you do need to ensure that
its public fields correspond exactly in name and type to a subset of the cmp-fields of the
bean. The JobPK class is shown in Listing 7.9.
09 0672323842 CH07
3/20/02
12:20 PM
Page 309
CMP and EJB QL
LISTING 7.9
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:
309
JobPK Class
package data;
import java.io.*;
import javax.ejb.*;
public class JobPK implements Serializable
{
public String ref;
public String customer;
public JobPK() {
}
public JobPK(String ref, String customer) {
this.ref = ref;
this.customer = customer;
}
public String getRef() {
return ref;
}
public String getCustomer() {
return customer;
}
// code omitted
}
The EJB container will match the ref and customer fields with getRef()/setRef() and
getCustomer()/setCustomer() accessor methods for the cmp-fields. You can see here
that the fields in the primary key class must be declared to have public visibility.
Moving onto the other finder methods, the findByCustomer() method has the following
signature in the local-home interface:
Collection findByCustomer(String customer) throws FinderException;
The EJB QL query for this is as follows:
SELECT OBJECT(j)
FROM Job AS j
WHERE j.customer = ?1
The other finder method is findByLocation(), whose signature is as follows:
Collection findByLocation(String location) throws FinderException;
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 310
310
Day 7
The EJB QL query for this is as follows:
SELECT OBJECT(;)
FROM Job AS ;
WHERE ;.location.name = ?1
Home Methods
The last method in the local-home interface is the home method deleteByCustomer().
This is used by clients when removing a customer; all of its jobs must also be removed.
You’ve already seen the implementation of this home method under BMP, using SQL to
delete from the Job and JobSkill tables. Under CMP, the implementation is somewhat
more object-oriented, as shown in Listing 7.10.
LISTING 7.10
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:
JobBean’s ejbHomeDeleteByCustomer() Method
package data;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public void ejbHomeDeleteByCustomer(String customer) {
try {
Collection col = this.jobHome.findByCustomer(customer);
for (Iterator iter = col.iterator(); iter.hasNext(); ) {
JobLocal job = (JobLocal)iter.next();
// remove job from collection
iter.remove();
// remove job itself
job.remove();
}
}
catch (FinderException e) {
error(“Error removing all jobs for “ + customer, e);
}
// needed because of the explicit job.remove()
catch (RemoveException e) {
error(“Error explicitly removing job for “ + customer, e);
}
}
// code omitted
}
09 0672323842 CH07
3/20/02
12:20 PM
Page 311
CMP and EJB QL
311
The method calls the bean’s own findByCustomer() method, which returns a collection
of jobs for the customer. It then iterates over these jobs and removes them one-by-one.
Caution
The jobHome field holds a reference to the bean’s own home interface. In
principle, the entityContext.getEJBHome() method could have been called,
but the J2EE RI 1.3 container seems always to return null.
If there had been no suitable finder method, this would have been a prime case for using
a select method. The select method would be declared in the JobBean class as follows:
package data;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public abstract Collection ejbSelectByCustomer(String customer);
// code omitted
}
The EJB QL query would be identical to that of the finder method. The only other difference would be in how the method was called. Rather than invoking the finder method
through the home interface, the ejbSelect method could be called directly. So, in the
ejbHomeDeleteByCustomer() method, the line
Collection col = this.jobHome.findByCustomer(customer);
would be replaced with
Collection col = this.ejbSelectByCustomer(customer);
In fact, the BMP and CMP implementations are not quite comparable; the CMP implementation is a little more robust. As was mentioned earlier, the BMP version of this method just
deleted the appropriate rows from the Job and JobSkill table (turn back to yesterday’s
chapter to see this, if needed). This means that the Job bean’s ejbRemove() method is
never called; there is no opportunity for the bean to perform clean-up processing.
7
09 0672323842 CH07
3/20/02
312
12:20 PM
Page 312
Day 7
Note
An alternative design again might have been to define a cascade delete
relationship from Customer through to Job. The EJB Specification does
require that ejbRemove() is called on every bean being deleted as a result of
the cascade, so the net result is similar to the CMP implementation.
However, as has been remarked earlier, setting up a (cascade delete) relationship for Customer and Job is not easy when using the J2EE RI, because it
does not easily support relationships between beans when one bean is identified by another (part of the primary key is also a foreign key).
Note
A cascade delete relationship might be preferable to the CMP implementation. A naïve EJB container implementation would work the same way as
the hand-coded CMP implementation, explicitly deleting each and every
child bean one-by-one. The performance hit could be substantial.
A more sophisticated EJB container implementation ought to be able to call
ejbRemove() for each bean, but then delete all of the child beans using a
single call to the persistent data store; in other words, combining the best of
the BMP and CMP approaches.
A related issue is that if a client happens to have a reference to a Job for the Customer
being deleted, they won’t find out that the Job has been removed until they attempt to
access that Job again. At that point, the client will receive a
java.rmi.NoSuchObjectException. But note that this isn’t a problem just with BMP;
the same behavior will occur for CMP also.
Implementing the Local Interface Methods
Looking back at the JobLocal interface back in Listing 7.3, you can see that many of the
methods in the local interface simply expose the bean’s cmp-fields or cmr-fields to its
clients. Of course, there is no implementation for these methods because they are implemented by the EJB container’s deployment tools. Therefore, all that is required is to copy
the methods over from the local interface and mark them abstract. If you cast your eyes
back all the way to Listing 7.1, you’ll see that this is precisely what was done.
The only method in the JobLocal interface that does not correspond to an accessor
method for a cmp-field or cmr-field is the getCustomerObj() method. Its implementation is shown in Listing 7.11.
09 0672323842 CH07
3/20/02
12:20 PM
Page 313
CMP and EJB QL
LISTING 7.11
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
313
JobBean’s getCustomerObj() Method
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
private CustomerLocal customerObj; // derived
public CustomerLocal getCustomerObj() {
return customerObj;
}
// code omitted
}
Pretty straightforward, though of course the hard work is done in ejbLoad() (Listing 7.5)
and ejbCreate() (Listing 7.7). Recall that the customerObj field holds the actual reference to the “parent” Customer for the Job and is derived from the customer cmp-field
that holds merely the name of the Customer. Because the customer cmp-field makes up
part of the primary key, it is immutable, and so is the customerObj field—hence, no
setCustomerObj() method.
Configuring a CMP Entity Bean
As you now appreciate, a substantial part of the implementation effort for CMP Entity
beans is simply completing the deployment descriptor correctly.
This will always involve completing the entity element of the deployment descriptor,
either manually or using a GUI tool such as deploytool. If the CMP Entity has
container-managed relationships, these too must be specified under the relationships
element of the deployment descriptor.
The entity Element
To remind you, the structure of the deployment descriptor is as follows:
<!ELEMENT ejb-jar (description?, display-name?, small-icon?, large-icon?,
enterprise-beans, relationships?, assembly-descriptor?, ejb-client-jar?)>
Here you can see the relationships element, with
<!ELEMENT enterprise-beans (session | entity | message-driven)+>
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 314
314
Day 7
and the entity element defined as
<!ELEMENT entity (description?, display-name?, small-icon?, large-icon?,
ejb-name, home?, remote?, local-home?, local?, ejb-class,
persistence-type,
prim-key-class,
reentrant,
cmp-version?, abstract-schema-name?,
cmp-field*, primkey-field?,
env-entry*,
ejb-ref*, ejb-local-ref*,
security-role-ref*, security-identity?,
resource-ref*,
resource-env-ref*,
query*)>
Looking first at the entity element, much of it will be unchanged. What needs to be
specified for a CMP entity are
Always set to 2.0. The value 1.1 is supported only for legacy CMP
Entity beans written to the EJB 1.1 specification.
•
cmp-version
•
abstract-schema-name
•
One for each cmp-field (but not cmr-fields). In the Job bean, the
are ref, customer, and description. The location and skills fields
are cmr-fields representing relations to the Location and Skill beans respectively, and so do not appear.
Any unique name, this defines the name used to identify
the bean in EJB QL queries. It makes sense to base this on the name of the bean.
In the case study, the JobBean bean has a schema with the name of Job.
cmp-field
cmp-fields
•
This optional field is used when the primkey-class element
does not identify a custom primary key class. It is not specified for the Job bean,
but for the Location bean, for example, it is specified and is set to name.
•
query
primkey-field
Defines an EJB QL query, associating it with a finder or select method.
Listing 7.12 shows the entity element for the Job bean.
LISTING 7.12
Job Bean’s entity Element
1: <entity>
2:
<display-name>JobBean</display-name>
3:
<ejb-name>JobBean</ejb-name>
4:
<local-home>data.JobLocalHome</local-home>
5:
<local>data.JobLocal</local>
6:
<ejb-class>data.JobBean</ejb-class>
7:
<persistence-type>Container</persistence-type>
09 0672323842 CH07
3/20/02
12:20 PM
Page 315
CMP and EJB QL
LISTING 7.12
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:
315
Continued
<prim-key-class>data.JobPK</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>Job</abstract-schema-name>
<cmp-field>
<description>no description</description>
<field-name>ref</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>description</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>customer</field-name>
</cmp-field>
<ejb-local-ref>
<ejb-ref-name>ejb/CustomerLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>CustomerLocalHome</local-home>
<local>CustomerLocal</local>
<ejb-link>data_entity_ejbs.jar#CustomerBean</ejb-link>
</ejb-local-ref>
<!-- shouldn’t be needed, but
➥ctx.getEJBHome() returns null in J2EE RI -->
<ejb-local-ref>
<ejb-ref-name>ejb/JobLocal</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>JobLocalHome</local-home>
<local>JobLocal</local>
<ejb-link>data_entity_ejbs.jar#JobBean</ejb-link>
</ejb-local-ref>
<security-identity>
<description></description>
<use-caller-identity></use-caller-identity>
</security-identity>
<query>
<description></description>
<query-method>
<method-name>findByCustomer</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<ejb-ql>SELECT OBJECT(j)
FROM Job AS j
WHERE j.customer = ?1</ejb-ql>
</query>
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 316
316
Day 7
LISTING 7.12
Continued
55:
<query>
56:
<description></description>
57:
<query-method>
58:
<method-name>findByLocation</method-name>
59:
<method-params>
60:
<method-param>java.lang.String</method-param>
61:
</method-params>
62:
</query-method>
63:
<ejb-ql>SELECT OBJECT(o)
64: FROM Job o
65: WHERE o.location.name = ?1</ejb-ql>
66:
</query>
67: </entity>
The definition of the query element is as follows:
<!ELEMENT query (description?, query-method, result-type-mapping?, ejb-ql)>
You can see from the listing that the query-method element just identifies the name of
the finder or select method. Note that if a finder method is being identified, it is the name
appearing in the local-home (or home) interface; that is, without the ejb prefix. On the
other hand, if a select method is being identified, there will be an ejb prefix, because
select methods never appear in the interfaces of the bean.
The result-type-mapping element applies only if the method identified by querymethod has identified a select method, and only then if the EJB QL string returns Entity
bean references (that is, if the SELECT [DISTINCT] OBJECT(x) style of select_clause
has been used). The allowable values are Local and Remote, indicating whether the
clause should return references through the local or remote interfaces. Obviously, if specified, the bean must provide an interface of the appropriate type; if not specified, the
bean must provide a local interface, because this is the implied value for the resulttype-mapping element.
Of course, all of this deployment information can be entered graphically using the
Figure 7.8 shows some of the equivalent information for Listing 7.12.
deploytool.
FIGURE 7.8
lets CMP
deployment information be defined through
a GUI.
deploytool
09 0672323842 CH07
3/20/02
12:20 PM
Page 317
CMP and EJB QL
317
The relationships Element
The relationships element is defined in the EJB 2.0 DTD as follows:
<!ELEMENT relationships (description?, ejb-relation+)>
That is, it consists of one or more ejb-relation elements. These in turn are defined as
<!ELEMENT ejb-relation (description?, ejb-relation-name?,
ejb-relationship-role, ejb-relationship-role)>
which is a somewhat curious definition: an optional description, an optional name, and
then precisely two ejb-relationship-role elements. Each of these identifies the role
that some bean is playing with respect to the relationship.
Note
It is possible that the same bean appears in both roles to model recursive
relationships.
The ejb-relationship-role element is defined as follows:
<!ELEMENT ejb-relationship-role (description?, ejb-relationship-role-name?,
multiplicity, cascade-delete?, relationship-role-source, cmr-field?)>
with
<!ELEMENT relationship-role-source (description?, ejb-name)>
You can see that the relationship-role-source element merely identifies an Entity
bean by name. This element’s name is perhaps somewhat misleading, because it is neither a “source” nor (for that matter) a target within the relationship. The navigability of
the relationship comes from the presence (or not) of the cmr-field element. Of course,
at least one of the ejb-relationship-role elements must have a cmr-field specified,
and if both do, the relationship is bi-directional.
The choices for the multiplicity element are either One or Many. There will be two such
elements in the complete ejb-relation element, so this is what gives one-to-one, oneto-many, many-to-one, or many-to-many.
There is also an optional cascade-delete element. Perhaps non-intuitively, this is placed
on the “child” (multiplicity = many) side of a relationship.
Finally, the cmr-field is defined as follows:
<!ELEMENT cmr-field (description?, cmr-field-name, cmr-field-type?)>
7
09 0672323842 CH07
3/20/02
12:20 PM
318
Page 318
Day 7
The cmr-field-name element just names the cmr-field (for example, skills or location for the Job bean). The cmr-field-type element is needed only for multi-valued
cmr-fields (for example, skills for the Job bean) and indicates whether the return type
is a java.util.Collection or java.util.Set.
Caution
Do not confuse this return type with the allowable return types for select
methods. These are unrelated areas that just happen to have the same
allowable return types.
Okay! Time for an example or two from the case study to see if all that theory makes
sense.
Listing 7.13 shows the ejb-relation element defining the one-to-many relationship
between the Location bean and the Job bean.
LISTING 7.13
The Location/Job Relationship
1: <ejb-relation>
2:
<ejb-relation-name></ejb-relation-name>
3:
<ejb-relationship-role>
4:
<ejb-relationship-role-name>JobBean</ejb-relationship-role-name>
5:
<multiplicity>Many</multiplicity>
6:
<relationship-role-source>
7:
<ejb-name>JobBean</ejb-name>
8:
</relationship-role-source>
9:
<cmr-field>
10:
<cmr-field-name>location</cmr-field-name>
11:
</cmr-field>
12:
</ejb-relationship-role>
13:
<ejb-relationship-role>
14:
<ejb-relationship-role-name>
➥
LocationBean
➥</ejb-relationship-role-name>
15:
<multiplicity>One</multiplicity>
16:
<relationship-role-source>
17:
<ejb-name>LocationBean</ejb-name>
18:
</relationship-role-source>
19:
<cmr-field>
20:
<cmr-field-name>jobs</cmr-field-name>
21:
<cmr-field-type>java.util.Collection</cmr-field-type>
22:
</cmr-field>
23:
</ejb-relationship-role>
24: </ejb-relation>
Figure 7.9 shows this relationship overlaid onto the implied abstract schema.
09 0672323842 CH07
3/20/02
12:20 PM
Page 319
CMP and EJB QL
319
FIGURE 7.9
The ejb-relation
element describes the
characteristics of a
one-to-many relationship in the abstract
schema.
Location
name
description
location
<ejb-relation-role>
<ejb-relationsip-role-name>
<multiplicity>
<relationship-role-source>/<ejb-name>
<cmr-field>/<cmr-field-name>
</ejb-relation-role>
location for
jobs
Skill
Job
customer
ref
name
requires
description
location Location.name(FK) jobs
skills
description
<ejb-relation-role>
<ejb-relationsip-role-name>
<multiplicity>
<relationship-role-source>/<ejb-name>
<cmr-field>/<cmr-field-name>
</ejb-relation-role>
Another example; Listing 7.14 shows the ejb-relation element defining the many-tomany relationship between the Job bean and the Skill bean.
LISTING 7.14
The Job/Skill Relationship
1: <ejb-relation>
2:
<ejb-relation-name></ejb-relation-name>
3:
<ejb-relationship-role>
4:
<ejb-relationship-role-name>JobBean</ejb-relationship-role-name>
5:
<multiplicity>Many</multiplicity>
6:
<relationship-role-source>
7:
<ejb-name>JobBean</ejb-name>
8:
</relationship-role-source>
9:
<cmr-field>
10:
<cmr-field-name>skills</cmr-field-name>
11:
<cmr-field-type>java.util.Collection</cmr-field-type>
12:
</cmr-field>
13:
</ejb-relationship-role>
14:
<ejb-relationship-role>
15:
<ejb-relationship-role-name>
➥
SkillBean
➥</ejb-relationship-role-name>
16:
<multiplicity>Many</multiplicity>
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 320
320
Day 7
LISTING 7.14
Continued
17:
<relationship-role-source>
18:
<ejb-name>SkillBean</ejb-name>
19:
</relationship-role-source>
20:
<cmr-field>
21:
<cmr-field-name>jobs</cmr-field-name>
22:
<cmr-field-type>java.util.Collection</cmr-field-type>
23:
</cmr-field>
24:
</ejb-relationship-role>
25: </ejb-relation>
Figure 7.10 shows these elements overlaid onto the abstract schema.
FIGURE 7.10
Location
The ejb-relation
element describes the
characteristics of a
many-to-many relationship in the abstract
schema.
name
description
location
<ejb-relation-role>
<ejb-relationsip-role-name>
<multiplicity>
<relationship-role-source>/<ejb-name>
<cmr-field>/<cmr-field-name>
</ejb-relation-role>
location for
jobs
Skill
Job
customer
ref
name
requires
description
location Location.name(FK) jobs
skills
description
<ejb-relation-role>
<ejb-relationsip-role-name>
<multiplicity>
<relationship-role-source>/<ejb-name>
<cmr-field>/<cmr-field-name>
</ejb-relation-role>
As always, configuring these deployment descriptor elements can be done either by editing the deployment descriptors directly or by using the deploytool. Figure 7.11 shows
the deploytool for the Job/Skill relationship.
To actually deploy the enterprise application, use the buildAll and deploy batch scripts
in the day07\build directory, or use buildAll to assemble the enterprise application and
deploy from deploytool.
09 0672323842 CH07
3/20/02
12:20 PM
Page 321
CMP and EJB QL
321
FIGURE 7.11
can be
used to configure relationships through its
GUI.
deploytool
Over the last three days, you have seen Session beans, BMP Entity beans and CMP
Entity beans deployed using both the graphical deploytool and then using batch scripts.
Clearly, the information held in those deployment descriptors is valuable and should be
under source code control. Moreover, the mechanism for building and deploying enterprise applications should be able to use that information rather than recreate it from
scratch. This suggests that, for the production environment, the graphical deploytool
should be used only for deploying enterprise applications and for configuring J2EE RI
servers.
On the other hand, in the development environment, it can be an error-prone task to
attempt to write XML deployment descriptors from scratch. If a valid deployment
descriptor already exists, modifying it (to add a new ejb-ref element or something similar) can often be accomplished, but larger changes (such as adding a completely new
bean) will be more difficult without much practice. Here, deploytool comes into its own
to modify the enterprise application as required (or indeed, to create an enterprise application from scratch).
When you are happy that the beans and clients in your enterprise application are correctly configured, deploytool allows the XML deployment descriptor to be saved, for
checking into source code control. This is shown in Figure 7.12.
FIGURE 7.12
allows
deployment descriptors
to be saved as XML
files.
deploytool
7
09 0672323842 CH07
3/20/02
12:20 PM
322
Page 322
Day 7
The Tools, Descriptor Viewer menu option brings up a dialog box displaying the XML
deployment descriptor; from there, the data can be saved as a file. This menu option is
context sensitive, so what it shows will depend on the node selected in the explorer on
the left pane in the GUI. The descriptor viewer dialog should be brought up for each
node under the enterprise application node, and for the enterprise application node itself.
In the case study, this means for each of the clients, for the data Entity EJBs, for the
agency Session EJBs, and for the agency application node.
Deploying a CMP Entity Bean
The enterprise application can be deployed either from the command line (deploy script
in the build directory) or using deploytool itself.
However, before an enterprise application containing CMP Entity beans can be deployed,
the default SQL must be generated by using the Deployment Settings dialog box. This
is performed once for each CMP Entity bean, as shown in Figure 7.13.
FIGURE 7.13
allows
SQL to create the
database schema to be
generated.
deploytool
You can see from the figure that J2EE RI generates default SQL. It allows the SQL query
for the finder and select methods to be tuned, and also (the container methods radio
button) allows the actual SQL to create the tables, insert rows, and so on to be modified
also. The case study does not change any of this default SQL.
Note
Right at the beginning of today’s chapter, it was noted that the schema of
the database for the case study had changed from that of Day 6. If one wanted to use the exact schema from Day 6, it could have been entered here.
09 0672323842 CH07
3/20/02
12:20 PM
Page 323
CMP and EJB QL
323
Also, the dialog allows the underlying tables to be created and deleted on deploy/undeploy. This obviously isn’t appropriate for a production environment, because it would
delete any data already there. It also is not appropriate for the case study, because there is
example data.
Note
The CreateCMPAgency script (provided in the day07\Database directory) creates exactly the same schema as that generated by default by J2EE RI. It also
populates that schema with the same data as in Day 6 and creates views for
backwards compatibility.
You will recall that the auxiliary deployment descriptor agency_ea-sun-j2ee-ri.xml
contains all the mappings of the logical dependencies of the EJBs to the physical runtime
environment. This includes all of the SQL specified in Figure 7.13.
It was noted earlier that when creating a new CMP Entity bean, it is often easiest to load
the enterprise application into deploytool and then save the XML deployment descriptor
using the Tools, Descriptor Viewer menu option/dialog. Unfortunately, deploytool does
not provide any easy way to write out the auxiliary deployment descriptor, and it is
required for the command line approach. The buildAll script calls the addJ2eeRiToEar
script that does precisely this.
The only real option is to save the agency.ear file once modified, and then use a tool,
such as WinZip, to load up the EAR file. The auxiliary deployment descriptor can be
extracted from that.
Patterns and Idioms
This section presents some patterns and idioms that relate to CMP Entity beans. You’ll
recognize some of the points made here; they were made earlier in the “Container
Managed Relationships” section.
Normalize/Denormalize Data in ejbLoad()/ejbStore()
Under CMP, the ejbLoad() and ejbStore() methods don’t have very much (or indeed
anything) to do; the interactions with the persistent data store are done by the EJB container.
However, it may be that the physical schema of the persistent data store (especially if
that persistent data store is an RDBMS) does not correspond exactly with the logical
schema of the Entity bean.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 324
324
Day 7
For example, the Applicant table defines two columns—address1 and address2.
However, at least conceptually, the Applicant Entity bean has a vector field of address,
of type String[]; there could be many lines in the address (and it’s just that the physical schema of the persistent data store constrains the size of this vector to 2):
Santa Claus
No. 1 Grotto Square
Christmas Town
North Pole
The World
Earth
The Solar System
(line
(line
(line
(line
(line
(line
1)
2)
3)
4)
5)
6, and so on …)
Because the ejbLoad() method is called after the EJB container has loaded the data, it
may renormalize the data. In other words, the data in the two cmp-fields of address1
and address2 can be converted into the String[] address field. The bean’s clients’ view
(as presented by the methods of the local interface) is that the address field is a vector.
Conversely, the ejbStore() method, called just before the EJB container updates the
data, can denormalize the data. In other words, the data in the address vector field can
be “posted” back to the address1 and address2 cmp-fields.
Don’t Expose cmp-fields
Although the EJB specification allows cmp-fields to be exposed in the local (or remote)
interface of a CMP Entity bean, there are problems with doing so. Because the setter
method that corresponds to the field is generated by the EJB container, it is not possible
to perform any application-level validation.
Instead, it is better to create a shadow setter method, have it do any validation, and then
delegate to the actual cmp-field setter method.
You may also want to create a shadow getter method. This would allow you symmetry in
the names of the methods, and you could also perhaps do some caching of values or
other application-level logic.
As an example, instead of exposing the getter and setter methods for the description
of the Job bean, you might have a local interface of
cmp-field
package data;
import javax.ejb.*;
// imports omitted
public interface JobLocal extends EJBLocalObject {
String getDescriptionField();
void setDescriptionField(String description);
09 0672323842 CH07
3/20/02
12:20 PM
Page 325
CMP and EJB QL
325
// code omitted
}
with a corresponding implementation of
package data;
import javax.ejb.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public String getDescriptionField() {
// any application logic here
return getDescription();
}
public void setDescriptionField(String description) {
// any application logic and validation here
setDescription(description);
}
public abstract String getDescription();
public abstract void setDescription(String description);
}
Don’t Expose cmr-fields
Although the EJB specification allows cmr-fields to be exposed in the local interface of
a CMP Entity bean, it may be best not to. There are two reasons why exposing the cmrfield causes problems, both related to the returned collection from the getter method of
a cmr-field:
• The first is that this returned collection is mutable. A client can change of the
Entity bean’s relationships with other beans by manipulating this collection. In
other words, the bean’s state is changed without it being aware.
• The second is that the returned collection becomes invalid when the transaction
context changes. This is actually good that this is so, but it is a subtle point, and
some developers might not appreciate it if less than familiar with EJB transactions
(making debugging their application somewhat tricky).
An alternative to exposing the setter method of a cmr-field would be for the bean to
offer alternative methods, such as addXxx() and removeXxx(), on the bean itself and
have these call the setter method. An alternative to exposing the getter method would be
to expose a shadow method called something like getXxxCopy(). This would create a
copy of the collection. The method name suggests to the client that they will not be able
to change the relationships of the bean. Indeed, the returned collection could even be
made immutable.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 326
326
Day 7
As an example, instead of exposing the getter and setter methods for the skills
of the Job bean, you might have a local interface of
cmp-field
package data;
import javax.ejb.*;
import java.util.*;
// imports omitted
public interface JobLocal extends EJBLocalObject {
Collection getSkillsCopy();
void addSkill(SkillLocal skill);
void removeSkill(SkillLocal skill);
// code omitted
}
with a corresponding implementation of
package data;
import javax.ejb.*;
import java.util.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public Collection getSkillsCopy() {
List skills = new ArrayList();
for(Iterator iter = getSkills().iterator(); iter.hasNext(); ) {
skills.add(iter.next());
}
return Collections.unmodifiableList(skills);
}
public void addSkill(SkillLocal skill) {
getSkills().add(skill);
}
public void removeSkill(SkillLocal skill) {
getSkills().remove(skill);
}
public abstract Collection getSkills();
public abstract void setSkills(Collection skills);
// code omitted
}
Enforce Referential Integrity Through the Bean’s
Interface
Entity beans represent the domain layer in the n-tier architecture, and Entity beans have relationships among themselves. If a method in a bean’s interface has an argument of some bean
(usually for a relationship), this bean should be defined via the local reference rather than by
its primary key. In other words, referential integrity is effectively enforced; the client guarantees that the bean exists, because it passes through an actual reference to that bean.
09 0672323842 CH07
3/20/02
12:20 PM
Page 327
CMP and EJB QL
327
This idiom is honored implicitly for CMP Entity beans that expose their cmr-fields. For
CMP Entity beans that provide shadow methods (as discussed earlier), these shadow
methods should still deal with local references to the related beans, rather than identifying the related bean by its primary key.
Note
This idiom applies equally to BMP Entity beans. Indeed, if BMP Entity beans
are written to follow this idiom, it becomes that much easier to convert
them to CMP.
Use Select Methods to Implement Home Methods
Select methods can only be called by a bean itself, so they act as helper methods. A common place where they are often used is in implementing home methods.
For example, the Job bean could have provided a home interface to count the number of
jobs advertised. This could have been implemented as follows:
package data;
import javax.ejb.*;
// imports omitted
public interface JobLocal extends EJBLocalObject {
int countJobs();
// code omitted
}
with a corresponding implementation of
package data;
import javax.ejb.*;
import java.util.*;
// imports omitted
public abstract class JobBean implements EntityBean {
public int ejbHomeCountJobs() {
int count = 0;
for (Iterator iter = ejbSelectAllJobs().iterator(); iter.hasNext(); ) {
iter.next(); count++;
}
return count;
}
public abstract Collection ejbSelectAllJobs();
// code omitted
}
7
09 0672323842 CH07
3/20/02
12:20 PM
328
Page 328
Day 7
The ejbSelectAllJobs() EJB QL query string would be simply
SELECT OBJECT(j)
FROM Jobs AS j
EJB QL does not (yet) support a SELECT COUNT(*) syntax, so this is the only
way of performing counts (other than resorting to direct access to the data
store).
Note
Gotchas
The following is a quick checklist of “gotchas” to help you with your implementation:
• If the primary key is composite, a custom primary key class must be defined. The
fields of this class must be public and must correspond in name and type to cmpfields of the bean class.
•
and cmr-field fields must begin with a lowercase letter, (so that it can
be capitalized in the corresponding getter and setter method names).
cmp-field
• If a collection is returned from a cmr-field’s getter method, it must not be modified other than through Iterator.remove() (see EJB specification, section
10.3.6.1).
• Collections returned by getter methods cannot be used outside of the transaction
context in which they were materialized.
• If a bean has no relationships, the Collection returned by the cmr-field’s getter
method will be empty, it will not be null. Conversely, if calling a cmr-field’s setter method, null cannot be used; instead an empty collection (such as
Collections.EMPTY_LIST) must be passed in.
• EJB QL strings are in single quotes!!!
• EJB QL strings define placeholders as ?1, ?2, and so on (rather than the JDBC syntax of just ?). This allows the arguments of the corresponding finder or select
method to be bound in more than once.
• When comparing strings in EJB QL, the strings must be identical to be equal. (This
is different from SQL where trailing whitespace is usually ignored.)
• An EJB QL empty string is ‘’ (0 characters). Some RDBMS treat this as a null
string, so beware! (See EJB Specification, section 11.2.9.)
09 0672323842 CH07
3/20/02
12:20 PM
Page 329
CMP and EJB QL
329
Summary
Well done! In three days (Days 5, 6, and 7), you’ve covered pretty much everything you
need to know about writing EJBs. There’s a little mopping up of relatively minor issues
tomorrow, but you now well and truly have the essentials under your belt.
Today you saw how the n-tier architecture is subtly revised again, with the responsibility
for persistence delegated downwards (into subclasses) to the EJB container-generated
subclasses of the CMP Entity bean. You also learned that the lifecycle for CMP Entity
beans is substantially the same as BMP Entity beans and, in general, there is less coding
to be done in the lifecycle methods (ejbCreate(), ejbLoad(), ejbStore(), and
ejbRemove()).
You now know that cmp-fields are defined in the deployment descriptor and correspond
to primitive and to Serializable objects. They are represented in the CMP bean class as
abstract getter and setter methods. You also know that cmr-fields work very much the
same way but deal with references to the local interfaces of other Entity beans (or collections thereof). This is what makes local interfaces the basis of container-managed relationships.
Finally, you saw how to construct EJB QL queries and how to relate them to both finder
methods and to the helper select methods.
Q&A
Q What does CMP stand for?
A CMP stands for Container Managed Persistence.
Q Can the getter and setter methods of cmp-fields and cmr-fields appear in the
bean’s interfaces?
A
cmp-field methods can appear in any interface; cmr-field methods can appear
only in local interfaces.
Q What keywords or features of ANSI SQL does EJB QL not (yet) support?
A The keywords and features EJB QL doesn’t support include count(*), union,
group by, order by (among others).
Q How are the parameters of a select method defined?
A The select method is written as a public method in the form ejbSelect() in the
class itself.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 330
330
Day 7
Q How is navigability defined in the deployment descriptor?
A The presence of the cmr-field element indicates navigability.
Exercises
The job agency case study defines a complete set of Session beans, and Entity beans. All
of the Entity beans except the Applicant bean are implemented using CMP; the
Applicant bean has been implemented using BMP. The exercise is to implement an
Applicant Entity bean to use CMP. The source code and deployment descriptors can be
found under day07\exercise, and the directory structure is the same as yesterday.
In more detail, the fields of the Applicant bean need to be converted into either cmpfields or cmr-fields:
•
This is the primary key for the Applicant Entity bean and will be a cmp-
login
field.
•
name
•
email
•
summary
•
location
•
skills
cmp-field
cmp-field
cmp-field
to the Location bean. There will be a one-to-many relationship between Location to Applicant.
cmr-field
cmr-field to the Skill bean. There will be a many-to-many relationship
between the Applicant and Skill beans.
You should find that the Job CMP Entity bean acts as a good model for your new version
of the Applicant bean. One difference is in the primary key. The Job bean required a
JobPK because it had a composite primary key. For your Applicant bean, there is no custom primary key, so you will need to nominate the login cmp-field as its primary key
using the primkey-field element in the deployment descriptor. Another related difference is that the Job bean has to deal with ensuring that its customer cmp-field is a valid
identifier for a Customer. Your Applicant bean will not have this complication.
There should be no need to change the Agency and Register Session beans that call the
bean, because it is only the internal implementation that is changing, not the
interfaces.
Applicant
The steps you should perform are as follows:
1. If you didn’t do so earlier today, convert the Agency database to its CMP version.
The steps to do this were described in the “A Quick Word about the Case Study
Database” section.
09 0672323842 CH07
3/20/02
12:20 PM
Page 331
CMP and EJB QL
331
2. Re-acquaint yourself with the ApplicantLocalHome and ApplicantLocal interfaces. However, these will not need to be changed.
3. Update the ApplicantBean class; base this on JobBean.
4. The CMR relationships that will be built will be bi-directional. This is needed
because, otherwise, the code generated by the J2EE RI fails to compile. So, in each
of LocationBean and SkillBean, uncomment the getApplicants() and
setApplicants() methods. Note that these have not been exposed in the respective
interfaces.
5. Build the enterprise application (agency.ear in the jar directory) and then load it
into deploytool. Delete the Applicant bean from the enterprise application, and
then add it in again into the data_entity_ejbs ejb-jar. As you add it, you can
specify that it is a CMP bean.
6. Specify the appropriate cmp-fields for the new bean.
7. Add a many-to-many relationship from Applicant to Skill. Make the relationship
bi-directional.
8. Add a many-to-one relationship from Applicant to Location. Make the relationship bi-directional.
9. Use the deploytool’s Descriptor Viewer dialog to save the XML deployment
descriptor.
10. Now deploy the enterprise application (from the deploytool GUI), providing the
necessary auxiliary deployment information in the wizard that is displayed. Test
your program by using the AllClients client, run with run\runAll.
11. Optionally, use WinZip or equivalent to extract the auxiliary deployment descriptor
and save as dd\agency_ea-sun-j2ee-ri.xml from the agency.ear file previously
generated. Then re-build and deploy using build\buildAll and build\deploy.
Good luck. A working example can be found in day07\agency.
7
09 0672323842 CH07
3/20/02
12:20 PM
Page 332
10 0672323842 Week2
3/20/02
9:27 AM
Page 333
WEEK 2
Developing J2EE
Applications
8
9
8 Transactions and Persistence
9 Java Messaging Service
10
10 Message-Driven Beans
11 JavaMail
12 Servlets
11
13 JavaServer Pages
14 JSP Tag Libraries
12
13
14
10 0672323842 Week2
3/20/02
9:27 AM
Page 334
11 0672323842 CH08
3/20/02
9:29 AM
Page 335
WEEK 2
DAY
8
Transactions and
Persistence
You have spent the last three days covering EJBs in detail. In particular, you
learned how to specify, implement, configure and deploy container-managed
persistence (CMP) Entity beans. Along with BMP Entity beans (Day 6, “Entity
EJBs”) and Session beans (Day 5, “Session EJBs”), you now have a good
appreciation of the EJB technology.
EJBs have been called transactional middle-tier components. Until now, you
haven’t had to worry too much about transactions in an EJB context because
they are reasonably transparent. In fact, you have been using containermanaged transaction demarcation. However, for those cases where you require
explicit control, EJB provides two solutions. You can write beans that manage
their own transactions—bean managed transaction demarcation—or you can
also extend the lifecycle of Session beans to give them visibility of the transaction demarcations. You will be learning about this today.
11 0672323842 CH08
336
3/20/02
9:29 AM
Page 336
Day 8
You spent Day 6 and Day 7, “CMP and EJB QL,” comparing the two different persistence approaches offered by EJB in the guise of BMP and CMP Entity beans. The BMP
Entity beans were implemented using JDBC, but that is only one of a number of technologies offered by J2EE and Java in general. Today, you will learn
• How to manage transactions explicitly in EJBs
• How transactions are managed “behind the scenes” in an EJB environment
• Some other persistence technologies other than JDBC—specifically, SQLj and
JDO
Overview of Transactions
If you’ve used RDBMS before, or completed a Computer Studies course or read any
other J2EE book, you’ve probably read a section like this one already. But read on anyway, if only to be acquainted with some of the J2EE terminology that is used in this
arena.
A transaction is an atomic unit of work:
• Atomic unit means indivisible—either every step within the transaction completes
or none of them do.
• Work here usually means some modification to data within a persistent data store.
In RDBMS terms, this means one or more INSERT, UPDATE, or DELETEs. However,
strictly speaking, it also applies to reading of data through SELECT statements.
For a persistent data store to support transactions, it must pass the so-called “ACID” test:
• Atomic—The transaction is indivisible; the data store must ensure this is true.
• Consistent—The data store goes from one consistent point to another. Before the
transaction, the data store is consistent; afterwards, it is still consistent.
The age-old example is of transferring money between bank accounts. This will
involve two UPDATEs, one decrementing the balance of account #1 and the other
incrementing the balance of account #2. If only one UPDATE occurs, the transaction
is not atomic, and the data store is no longer in a consistent state.
• Isolation—The data store gives the illusion that the transaction is being performed
in isolation. Enterprise applications have many concurrent users who are all performing transactions at the same time, so behind the scenes, the data store uses techniques, such as locks, to serialize access to the contended data where necessary.
• Durability—If a transaction completes, any changes made to the data as a result of
that transaction must be durable. In other words, if the power were to fail a millisecond after the transaction has completed, the change must still be there when
power is reconnected.
11 0672323842 CH08
3/20/02
9:29 AM
Page 337
Transactions and Persistence
Many data stores use transaction logs to address this requirement. The transaction
log holds a journal of changes made to the actual data. When the transaction completes, the log is written to disk, although the actual data need not be.
Note
If you are a Windows user, you may know that Windows NT, 2000 and XP
support a filesystem type called NTFS. This replaces the FAT and FAT32
filesystem types used in Windows 95, 98, and ME.
Microsoft often say that NTFS is more reliable, although slightly slower than
FAT/FAT32. This is because NTFS has a built-in transaction log, whereas
FAT/FAT32 does not.
Many data stores allow transactions to be started explicitly using a syntax such as the
following:
begin transaction t1
where t1 is the (optional) name transaction. Transactions are completed using either
commit (make changes permanent) or rollback (undo all changes made in the transaction, and revert all data back to the state before the transaction began). Many data stores
will use
commit transaction t1
and
rollback transaction t1
Note
Some data stores support the concept of nested transactions, whereby (for a
single user) one transaction can be started while another transaction is still
in progress. In other words, two begin transactions can be submitted without a commit or rollback between them.
However, the EJB specification and many others support only flat transactions, whereby one transaction must be completed before another is begun
(see EJB specification, section 17.1.2). Consequently, nested transactions are
not considered further.
To conclude this short introduction, consider the fragment of SQL shown in Listing 8.1.
It transfers $50 from account #20457 to account #19834.
337
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 338
338
Day 8
LISTING 8.1
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Example Fragment of SQL to Transfer Money Between Accounts
begin transaction transfer_money
update account
set balance = balance - 50
where account_id = 20457
update account
set balance = balance + 50
where account_id = 19834
commit transaction transfer_money
In effect, there are two different types of commands:
• Lines 1 and 11 demarcate the transaction.
• Lines 3–5 and 7–9 modify data.
A conventional RDBMS processes all of the SQL in Listing 8.1, but it is performing two
different roles in doing so. To understand and process the transaction demarcation commands, it is acting as a transaction manager. To understand and process the remaining
lines, it is acting as a resource manager.
In the EJB specification, these two responsibilities are split. In principle, you can think
of the EJB container as acting as the transaction manager, and the persistent data store
acting only as the resource manager. The term transaction coordinator is sometimes used
instead of transaction manager because there could be more than one resource whose
transactions are being coordinated.
Splitting the responsibilities of transaction management and resource management has
two consequences. For the bean provider, it means that to start a transaction, the bean
must interact both with the EJB container and with the persistent data store. The former
interaction is largely implicit because it is configured through the deployment descriptor
(and as you know, the latter interaction is implicit if CMP Entity beans are used). The
other consequence is that, for the persistent data store, it must defer all transaction control responsibility up to the EJB container. Behind the scenes, there is some quite sophisticated communication going on; you’ll learn a little about this activity later on today.
Container-Managed Transaction Demarcation
You’ve spent the last three days writing and deploying EJBs without really having to
worry too much about transactions. This isn’t to say that there have been no transactions
in use; far from it. Every interaction with the database performed in the case study has
11 0672323842 CH08
3/20/02
9:29 AM
Page 339
Transactions and Persistence
339
involved transactions. However, the Session and Entity beans deployed have used container manager transaction demarcation (here referred to as CMTD, though the abbreviation isn’t used in the EJB specification). Information in the deployment descriptor indicates when the EJB container should start and commit transactions.
Figure 8.1 shows a diagram that you saw first on Day 6.
FIGURE 8.1
The EJB proxy objects
implement transaction
(and security) control.
home
stub
home
remote
stub
remote
remote
client
security and
transactions
location
transparency
bean
local
home
local
client
local
This shows how the EJB proxy objects (those implementing the javax.ejb.EJBObject
or javax.ejb.EJBLocalObject interfaces) implement the transaction semantics. This is
one of the reasons that a bean must never implement its own remote interface. To do so
would mean that it could unwittingly return a reference to itself as a Remote interface,
subverting any security and transaction checks performed by its proxy.
Listing 8.2 shows a fragment of the deployment descriptor for the AdvertiseJob Session
bean from Day 7.
LISTING 8.2
Deployment Descriptor for AdvertiseJob Session Bean
1: <ejb-jar>
2:
<display-name>Agency</display-name>
3:
<enterprise-beans>
4:
<session>
5:
<display-name>AdvertiseJobBean</display-name>
6:
… lines omitted …
7:
<transaction-type>Container</transaction-type>
8:
… lines omitted …
9:
</session>
10:
</enterprise-beans>
11:
<assembly-descriptor>
12:
<container-transaction>
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 340
340
Day 8
LISTING 8.2
Continued
13:
<method>
14:
<ejb-name>AdvertiseJobBean</ejb-name>
15:
<method-intf>Remote</method-intf>
16:
<method-name>updateDetails</method-name>
17:
<method-params>
18:
<method-param>java.lang.String</method-param>
19:
<method-param>java.lang.String</method-param>
20:
<method-param>java.lang.String[]</method-param>
21:
</method-params>
22:
</method>
23:
<trans-attribute>Required</trans-attribute>
24:
</container-transaction>
25:
… lines omitted …
26:
</assembly-descriptor>
27: </ejb-jar>
As you have seen over the last three days, the enterprise-beans element consists of
session or entity elements. The session element has a transaction-type element which,
under CMTD, should have the value of Container. For entity elements, the transactiontype is not specified because entity beans must always be deployed using CMTD, so it is
implicit.
Each method of the remote interface must be present in a container-transaction element. The same is true of the methods in the home interface. The relevant DTD definitions that govern the structure of the deployment descriptor are as follows:
<!ELEMENT ejb-jar (description?, display-name?, small-icon?, large-icon?,
enterprise-beans, relationships?, assembly-descriptor?, ejb-client-jar?)>
The assembly-descriptor element is defined as follows:
<!ELEMENT assembly-descriptor (security-role*, method-permission*,
➥container-transaction*, exclude-list?)>
You can see here that assembly-descriptor element is all about providing the information used to create the EJB proxy objects. The security-role, method-permission, and
exclude-list elements are security related, and the container-transaction element
obviously defines transaction-related information.
The security-related elements of the assembly-descriptor element are not considered
further here. However, the container-transaction element is relevant to the discussion.
It is defined in the DTD as follows:
<!ELEMENT container-transaction (description?, method+, trans-attribute)>
and the method element is defined as
<!ELEMENT method (description?, ejb-name, method-intf?,
➥method-name, method-params?)>
11 0672323842 CH08
3/20/02
9:29 AM
Page 341
Transactions and Persistence
341
So, a container-transaction element identifies one or more methods. The method element identifies a method in the home or remote interface; the method-intf element is
only needed in those rare occasions when there happens to be a method of the same
name in both the home and remote interfaces. The method-name must be specified,
although the value of * can be used as a convenient shortcut to indicate all methods. The
method-params element is optional and is used to distinguish between overloaded versions of the same method name. If not specified, the method element identifies all overloaded versions of the method with the specified name.
Finally, the bit that really matters. The trans-attribute element indicates the transactional characteristics to be enforced when the specified method is invoked. A transaction may
or may not be in progress; in the terminology of the EJB specification, there may or may
not be a current valid transactional context. When a method is invoked, the EJB container
needs to know what should occur. For example, if there is no transaction in progress, is one
needed to execute the method? Should a transaction be started automatically if there isn’t
one? Perhaps a transaction can be started even if another one is in progress? And so on.
There are six possible values; their semantics are shown in Table 8.1.
TABLE 8.1
Different CMTD Semantics Are Indicated by the trans-attribute Element
transattribute
Meaning
Notes
NotSupported
Method accesses a resource manager that
does not support an external transaction
coordinator. Any current transaction context
will be suspended for the duration of the method.
The EJB architecture does
not specify the transactional
semantics of the method.
Required
A transaction context is guaranteed. The
current transaction context will be used
if present; otherwise, one will be created.
Commonly used.
Supports
Use valid transaction context if available (acts
like Required). Otherwise, use unspecified
transaction context (acts like NotSupported).
Acts as either Required or
NotSupported. This makes
the Supports a highly dubious choice. The method
must guarantee to work in
the same way whether or
not there is a transaction
context available.
RequiresNew
A new transaction context will be created. Any
existing valid transaction context will be
suspended for the duration of the method.
Can reduce contention (for
example, for a bean that
generates unique IDs or for
a bean that writes to a log).
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 342
342
Day 8
TABLE 8.1
Continued
transattribute
Meaning
Notes
Mandatory
A valid transaction context must exist; an
exception will be thrown by the EJB container
otherwise. The transaction context will be used.
Useful for helper beans’
methods, designed to be
called only from another
bean.
Never
There must be no current transaction context.
An exception will be thrown by the EJB
container otherwise. The method invokes with
an unspecified transaction context.
Acts as NotSupported.
For Entity beans, only the Required, RequiresNew, and Mandatory trans-attribute values are recommended. The problem with NotSupported, Never, and (if invoked with no
current transaction context) Supports is that, in addition to performing the business
method, the EJB container must also perform the ejbLoad() and ejbStore() methods.
These will be performed with the same transactional context as the business method,
which is to say, with no transactional context. What might happen then is somewhat undefined, as the EJB specification is at pains to point out. Indeed, it goes so far as to list (in
section 17.6.5) four or five different ways in which the EJB container might decide to act.
Caution
Never use NotSupported, Never, or Supports as trans-attribute values with Entity beans.
As usual, the deploytool GUI can also be used to configure the information within the
deployment descriptor, as shown in Figure 8.2.
You are likely to find that the vast majority of your beans’ methods will use the
Required trans-attribute value. Indeed, this is the value that has been used in the case
study over previous days.
Although CMTD means that the EJB container automatically starts, suspends, and completes transactions, this is not to say that the beans themselves cannot influence the transactions. After all, if an Entity bean hits an error condition that means that the transaction
should be aborted, it needs some way to indicate this to the EJB container. As an example, consider the hackneyed example of withdrawing money from a bank account. If the
balance would go into the red, (or perhaps more likely, beyond an overdraft limit), the
Entity bean that represents the account would want to indicate that the transaction should
be aborted.
11 0672323842 CH08
3/20/02
9:29 AM
Page 343
Transactions and Persistence
343
FIGURE 8.2
8
lets CMT
characteristics be
defined on a permethod basis.
deploytool
Note
It’s interesting to compare the XML deployment descriptor approach to
using deploytool. The deployment descriptor places the security- and
transaction-related information in the assembly-descriptor element, away
from the definition of the beans themselves (which reside under the enterprise-beans element). This is interesting in its own right, because it ties
back to the different EJB roles you learned about back on Day 2, “The J2EE
Platform and Roles.” The intention is that the bean provider completes just
the information in the enterprise-beans element, while the application
assembler completes the information under the assembly-descriptor. This
allows an EJB component to be used in different applications, with different
transaction and security requirements.
In contrast, the deploytool does not differentiate between the two roles;
the transaction (and security) information are just two of the seven tabs on
the right side of the GUI, providing information about the selected EJB; the
other five tabs pertain to information found under the enterprise-beans
element.
To do this, the bean can use two transaction-related methods provided by its EJBContext.
In the case of a Session bean, this will be the javax.ejb.SessionContext passed in
through the setSessionContext() method, and for an Entity bean, this will be the
javax.ejb.EntityContext passed in through setEntityContext(). To remind you,
Figure 8.3 shows a UML class diagram illustrating the methods provided by these interfaces.
11 0672323842 CH08
3/20/02
9:29 AM
Page 344
344
Day 8
FIGURE 8.3
The EJBContext provides access to the current transaction.
interface
EJBContext
interface
javax.transaction.UserTransaction
+getEJBHome():EJBHome
+getEJBLocalHome():EJBLocalHome
+getEnvironment():Properties
+getCallerIdentity():Identity
+getCallerPrincipal():Principal
+IsCallerInRole(Identity:Identity):boolean
+IsCallerInRole(s:String):boolean
+getUserTransaction():UserTransaction
+setRollbackOnly():void
+getRollbackOnly():boolean
+begin():void
+commit():void
+getStatus():int
+rollback():void
+setRollbackOnly():void
+setTransactionTimeout(:int):void
!
interface
javax.transaction.Status
interface
EntityContext
interface
SessionContext
+getEJBLocalObject():EJBLocalO
+getEJBObject():EJBObject
+getPrimaryKey():Object
+setEJBLocalObject(pO:EJBObje
+getEJBLocalObject():EJBLocalO
+getEJBObject():EJBObject
+STATUS_ACTIVE:int
+STATUS_MARKED_ROLLBACK:int
+STATUS_PREPARED:int
+STATUS_COMMITTED:int
+STATUS_ROLLEDBACK:int
+STATUS_UNKNOWN:int
+STATUS_NO_TRANSACTION:int
+STATUS_PREPARING:int
+STATUS_COMMITTING:int
+STATUS_ROLLING_BACK:int
!
To cause the transaction to be aborted, the CMTD bean can call setRollbackOnly().
This instructs the EJB container to prevent the transaction from being committed. The
bean cannot rollback the transaction directly, because the transaction itself is “owned” by
the EJB container, not the bean. The getRollbackOnly() method obviously just indicates whether the transaction has been marked for rollback.
There is one other transaction-related method in EJBContext, namely
getUserTransaction(). However, this cannot be called by a CMTD bean, and any
attempt to do so will result in the EJB container throwing a
java.lang.IllegalStateException.
Note
The remaining methods in EJBContext provide access to the home interface(s) of the bean (getEJBHome() and getEJBLocalHome()) and to the security context (getCallerPrincipal(), isCallerInRole(String)). The other
methods have been deprecated.
One last point relating to CMTD beans—they must not make use of any resource manager-specific transaction management methods that would interfere with the EJB container’s own management of the transaction context. Consequently, a BMP Entity bean cannot call commit(), setAutoCommit(), and so on a java.sql.Connection object.
If your bean does need more fine-grained control over transactions, the bean must be
deployed using bean-managed transaction demarcation. This is discussed next.
11 0672323842 CH08
3/20/02
9:29 AM
Page 345
Transactions and Persistence
Bean Managed Transaction Demarcation
If an EJB is deployed using bean managed transaction demarcation (here referred to as
BMTD, though this abbreviation isn’t used in the EJB specification itself), the EJB container allows the bean to obtain a reference to a javax.transaction.UserTransaction
object using the EJBContext. You can see this in Figure 8.3.
Motivation and Restrictions
An EJB might need to be deployed under BMTD if the conditions on which a transaction
is started depend on some programmatic condition. It could be that one method starts the
transaction and another method completes the transaction.
However, the cases where BMTD is needed are few and far between. Indeed, they are so
rare that the EJB specification limits BMTD to Session beans. Entity beans can only be
deployed with CMTD. This makes sense because Entity beans represent persistent transactional data; the transaction context should be as deterministic as possible.
Moreover, if a stateless Session bean starts a transaction, it must also commit that transaction before the method completes. After all, the stateless Session bean will have no
memory of the client that just invoked its method after that method completes, so one
cannot expect that the transaction context is somehow miraculously preserved. If you
write a BMTD stateless Session bean that does not commit its own transaction, the EJB
container will rollback the transaction and throw an exception back to the client
(java.rmi.RemoteException for remote clients, or javax.ejb.EJBException for local).
Indeed, even with stateful Session beans, there is a restriction. Any current transaction in
progress when the BMTD Session bean is called will be suspended by the EJB container,
not propagated to the BMTD bean. It is possible that, from the bean’s perspective, there
is a current transaction, but that would refer to any transaction not committed when a
method on that bean was last called.
Using the Java Transaction API
When a Session bean is deployed under BMTD, there is an implementation choice as to
how it should manage its transactions. If interacting solely with an RDBMS, the Session
bean can manage the transactions directly through the JDBC API. Alternatively, it can
use the Java Transaction API, defined by the classes and interfaces in the javax.transaction and the javax.transaction.xa packages. The latter is to be preferred, if only
because transactional access to Java Messaging Service resources (you’ll be learning
more about these tomorrow and the day after) can only be performed through the JTA
API. Equally, servlets can also use the JTA API.
345
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 346
346
Day 8
The Java 2 Platform Enterprise Edition Specification, the document that
defines the interoperability of all the technologies that make up the J2EE platform, only discusses transaction interoperability in the context of the JTA API.
Note
If nothing else, the semantics of intermixing JDBC and JTA calls are not
exhaustively defined, so this should be avoided to minimize chances of
portability problems if moving to a different vendor’s EJB container.
For a Session bean to start a transaction, it should first call the getUserTransaction()
method of its SessionContext. You’ll recall that this was the method that throws an
exception under CMTD, but it is the centerpiece of transaction control under BMTD.
Obtaining a UserTransaction does not mean that a transaction has been started. Rather,
it must be started using the begin() method. The transaction can then be completed
using either the commit() or the rollback() method. The current status can also be
obtained using getStatus(). This returns an int whose meaning is defined by the
constants in the javax.transaction.Status interface. Some of the most common status
values are shown in Table 8.2.
TABLE 8.2
Some of the Constants Defined in javax.transaction.Status
Constant
Meaning
STATUS_NO_TRANSACTION
No transaction is active.
Typical actions
tran.begin()
to start new
transaction.
STATUS_ACTIVE
A transaction is active and can
be used.
Use resource manager.
tran.commit() to commit
tran.rollback() to rollback
STATUS_MARKED_ROLLBACK
A transaction is active, but has
been marked for rollback. Any
attempt to commit it will result
in a javax.transaction.
RollbackException being thrown.
tran.rollback()
Note
There are more constants in the Status interface than those listed in Table
8.2. Later today, (in the “Transactions: Behind the Scenes” section), you’ll be
learning about some of the “under-the-covers” mechanics of transaction
management; the full list is presented there.
Listing 8.3 shows a possible implementation for the updateDetails() method of
AdvertiseJob bean using BMTD.
11 0672323842 CH08
3/20/02
9:29 AM
Page 347
Transactions and Persistence
LISTING 8.3
1:
2:
3:
4:
5:
6:
7:
8:
BMTD Implementation of AdvertiseJobBean.updateDetails()
package agency;
import javax.ejb.*;
import javax.transaction.*;
// imports omitted
public class AdvertiseJobBean extends SessionBean {
public void updateDetails (String description,
➥String locationName, String[] skillNames) {
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
int initialTranStatus = beginTransactionIfRequired();
if (skillNames == null) {
skillNames = new String[0];
}
List skillList;
try {
skillList = skillHome.lookup(Arrays.asList(skillNames));
} catch(FinderException ex) {
error(“Invalid skill”, ex, initialTranStatus);
➥ // throws an exception
return;
}
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:
347
LocationLocal location=null;
if (locationName != null) {
try {
location = locationHome.findByPrimaryKey(locationName);
} catch(FinderException ex) {
error(“Invalid location”, ex, initialTranStatus);
➥ // throws an exception
return;
}
}
job.setDescription(description);
job.setLocation(location);
job.setSkills(skillList);
completeTransactionIfRequired(initialTranStatus);
}
private int beginTransactionIfRequired() {
UserTransaction tran = this.ctx.getUserTransaction();
// start a new transaction if needed, else just use existing.
// (simulates trans-attribute of REQUIRED)
int initialTranStatus;
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 348
348
Day 8
LISTING 8.3
Continued
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
try {
initialTranStatus = tran.getStatus();
switch(initialTranStatus) {
case Status.STATUS_ACTIVE:
// just use
break;
case Status.STATUS_NO_TRANSACTION:
// create
try {
tran.begin();
} catch(NotSupportedException ex) {
// shouldn’t happen (only thrown if asking for nested exception
// and is not supported by the resource manager; not attempting
// to do this here).
throw new EJBException(
➥ “Unable to begin transaction”, ex);
}
break;
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:
// code omitted; other Status’ covered later
default:
throw new EJBException(
“Transaction status invalid, status = “ +
➥ statusAsString(initialTranStatus));
}
} catch(SystemException ex) {
throw new EJBException(“Unable to begin transaction”, ex);
}
return initialTranStatus;
}
/**
* expects initialTranStatus to be either
➥ STATUS_NO_TRANSACTION or STATUS_ACTIVE;
* semantics undefined otherwise
*/
private void completeTransactionIfRequired(int initialTranStatus) {
UserTransaction tran = this.ctx.getUserTransaction();
// if transaction was started, then commit / rollback as needed.
// (simulates trans-attribute of REQUIRED)
if (initialTranStatus == Status.STATUS_NO_TRANSACTION) {
try {
if (tran.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
tran.rollback();
11 0672323842 CH08
3/20/02
9:29 AM
Page 349
Transactions and Persistence
LISTING 8.3
Continued
91:
92:
93:
94:
95:
96:
97:
98:
99: }
349
} else {
tran.commit();
}
} catch(Exception ex) {
throw new EJBException(
➥ “Unable to complete transaction”, ex);
}
}
}
The two helper methods, beginTransactionIfRequired() and
completeTransactionIfRequired(), isolate the actual transaction management code, so
it can be reused across different methods.
Deploying a BMTD Bean
Of course, when deploying a bean under BMTD, the deployment descriptor should indicate a transaction-type element of Bean, and you will not need any containertransaction elements under the application-assembly element. Figure 8.4 shows
deploytool for the AdvertiseJob bean, indicating this fact.
FIGURE 8.4
BMTD is indicated
through the deployment descriptor, as
shown in deploytool.
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 350
350
Day 8
Incidentally, if a BMTD Session bean calls getRollbackOnly() or setRollbackOnly()
on its SessionContext, the EJB container will throw a
java.lang.IllegalStateException. This is reasonable; if a BMTD has access to the
UserTransaction object, it has no need for these methods. Instead, it can call the
getStatus() method of UserTransaction, and explicitly call rollback() if needed.
Client-Demarcated Transactions
As well as Session beans managing their own transactions, it is also possible for clients
to initiate the transaction and have it propagate through to the EJBs. Here, “client” means
either an application client written using the Swing GUI (such as you have seen in the
case study), or it could equally refer to a Web-based client implemented using servlets
and JSPs.
For either of these clients, the EJB architecture requires that a UserTransaction context
can be obtained via JNDI, bound under the name of java:comp/UserTransaction. So
the code fragment shown in Listing 8.4 will do the trick.
LISTING 8.4
Obtaining a UserTransaction Object from JNDI
1:
2:
3:
4:
5:
// assuming:
// import javax.naming.*;
// import javax.transaction.*;
InitialContext ctx = new InitialContext();
UserTransaction tran = (UserTransaction)
➥ctx.lookup(“java:comp/UserTransaction”);
6: tran.begin();
7: // call session and entity beans
8: tran.commit();
That said, if you find yourself needing to use client-demarcated transactions, you should
look at your application design and see if you are happy with it. After all, Session beans
(are meant to) represent the application logic of your application, and this should surely
include defining the transactional boundaries of changes to persistent data. Application
clients should only provide a presentational interface to your application.
If that philosophical argument does not appeal, perhaps this might. A rogue client could
be coded such that it begins a transaction, interacts with (and therefore ties up) various
resources, such as Entity beans, and then not commit. This could seriously impact the
performance of your application.
Exceptions Revisited
On Days 5 and 6, you learned the appropriate exceptions for your EJB to throw. In
summary
11 0672323842 CH08
3/20/02
9:29 AM
Page 351
Transactions and Persistence
• To throw an application-level exception (indicating that a possibly recoverable condition has arisen), throw any checked exception (excluding
java.rmi.RemoteException).
• To throw a system-level exception (indicating that a non-recoverable severe condition has arisen), throw any java.lang.RuntimeException (usually a subclass of
javax.ejb.EJBException).
If an application-level exception is thrown by a bean, it is up to that bean whether the
current transaction is affected or not. If the bean takes no action other than raising its
exception, the current transaction will be unaffected. The exception will simply propagate back to the calling client.
However, CMTD beans may decide to mark the current transaction for rollback, meaning
that the “owner” of the transaction (the EJB container or some BMTD bean) will be
unable to commit that transaction.
If a BMTD bean hits an error condition, it has a choice. Because it “owns” the transaction, it can simply do a rollback. Alternatively, it might elect to keep the transaction
active.
If a system-level exception is thrown by a bean, this does have consequences for any current transaction. If any bean throws a system exception, the EJB container will mark the
current transaction for rollback. If that bean happens to be a CMTD bean, and the EJB
container started a transaction just before invoking the CMTD method (as a result of a
Required or RequiresNew trans-attribute), the EJB container will actually rollback
that transaction.
To summarize,
• An application-level exception may or may not leave the current transaction active;
use getStatus() or getRollbackOnly() to find out.
• A system-level exception will either mark the current transaction for rollback or
even do the rollback.
One last thing on transactions and exceptions. Most of the exceptions in javax.ejb
(CreateException, RemoveException, and so on) are application exceptions. Some of
these, especially with CMP Entity beans, are raised by the EJB container itself. Rather
unhappily, the EJB specification does not mandate whether these application exceptions
should mark any current transaction for rollback (see section 10.5.8). Instead, it just indicates that the getStatus() or getRollbackOnly() methods should be used to determine
the status of the current transaction. In practical terms, what this means is that different
EJB containers could have different implementations, compromising portability.
351
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 352
352
Day 8
Extended Stateful Session Bean Lifecycle
Occasionally, there is a need for a stateful Session bean to have visibility as to the
progress of the underlying transaction. Specifically, a bean might need to know
• Has the transaction started?
• Is the transaction about to complete?
• Did the transaction complete successfully or was it rolled back?
For example, consider the age-old example of a ShoppingCart bean. In its purchase()
method, it is likely to modify its internal state. Perhaps it holds its contents in a
java.util.List called currentContents. On purchase(), it might move the contents
of currentContents to another java.util.List called recentlyBought.
Suppose, then, that the transaction that actually modifies the persistent data store fails to
complete. Maybe the shopper doesn’t have enough limit left on his or her credit card.
Because Session beans are not transactional, the ShoppingCart bean needs to know that
it should reset its internal state back to the beginning of the transaction. In other words, it
needs to move the contents of the recentlyBought list back over to currentContents.
Obviously, there is no issue for stateful Session beans deployed under BMTD, because
they are the owner of the transaction anyway and know when the tran.begin() and
tran.commit() methods will be invoked. But for CMTD Session beans, there is an issue.
The EJB specification addresses this by extending the lifecycle of the bean. If a stateful
Session bean implements the javax.ejb.SessionSynchronization interface, three additional lifecycle methods are defined and will be called at the appropriate points:
•
afterBegin()
The transaction has just begun.
•
beforeCompletion()
•
The transaction has completed. The boolean argument has the value true to indicate that the transaction was committed, or false to
indicate that the transaction was rolled back.
The transaction is about to be committed.
afterCompletion(boolean)
Figure 8.5 is a reworking of Figure 5.14 that you saw back on Day 5. It shows the stateful Session bean’s view of its lifecycle. (The client’s view and the actual lifecycle managed by the EJB container are unchanged).
11 0672323842 CH08
3/20/02
9:29 AM
Page 353
Transactions and Persistence
FIGURE 8.5
353
pool too small/setSessionContext
The Session
Synchronization
interface gives the
stateful Session bean
visibility to the transactions managed under
CMTD.
Pooled
[timeout]
[surplus]
remove/ejbRemove
create/ejbCreate
Bound to client
Passivated
non-TX business method
[too many active]
/ejbPassivate
Ready
[business method or remove
invoked]/ejbActivate
[called in xactn]/afterBegin
commit/beforeCompletion
afterCompletion(true)
rollback/afterCompletion(false)
Ready in xact
TX business method
One common pattern for using this interface is to use the afterBegin() method to load
any data from the data store (perhaps in the form of Entity beans), and then use the
beforeCompletion() method to write any cached data that may have changed. One
immediate application might be with respect to multi-valued cmr-fields. You will recall
from yesterday that collections returned by the getter method of a cmr-field are valid
only for the duration of a transaction. These two methods scope the duration that such a
returned collection can be used.
There are analogies here also with SQL, where the afterBegin() corresponds to a
statement, and beforeCompletion() corresponds to the
UPDATE statements.
SELECT … WITH HOLDLOCK
If the SessionSynchronization interface is implemented by a Session bean, the only
allowable values for the trans-attribute element in its deployment descriptor are
Required, RequiresNew, or Mandatory. This is because these are the only attributes that
can guarantee the presence of a transaction.
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 354
354
Day 8
Transactions: Behind the Scenes
The EJB specification starts off by listing nine goals. The second of these reads as
follows:
“The Enterprise JavaBeans architecture will make it easy to write applications:
Application developers will not have to understand low-level transaction and state
management details, multi-threading, connection pooling, and other complex lowlevel APIs.”
The EJB specification (downloadable from
http://java.sun.com/products/ejb/index.html)
largely succeeds in addressing this
goal—as a developer, you really do not need much knowledge about how transactions
are managed. The fact that this book only covers transactions today is testament to that.
Nevertheless, like most technical topics, it can be helpful to have an insight as to what is
going on “behind the scenes.” But if you want to skip this material and make a shorter
day of it, please do so. You can always re-read at a later date if you find yourself wanting
to know more.
Transaction Managers, Resource Managers, and 2PC
You already know about the terms resource manager and transaction manager (or transaction coordinator). In most EJB applications, an RDBMS will take the place of a
resource manager, and the EJB container itself will be the transaction manager.
This division of responsibilities is required because in an EJB architecture, the data used
by Session beans or represented by Entity beans may reside in more than one persistent
data store. For example, one Entity bean might map onto an Oracle RDBMS, and another Entity bean may map onto a Sybase RDBMS, as shown in Figure 8.6. (The numbers
will be explained shortly.)
FIGURE 8.6
The J2EE platform
separates resources
and transaction managers.
2.
local
client
entity
bean
3.
Ora
session
bean
1.
5.
entity
bean
Syb
transaction manager
4.
6.
7.
11 0672323842 CH08
3/20/02
9:29 AM
Page 355
Transactions and Persistence
Leaving the transaction management responsibilities with the RDBMSs is not suitable.
Doing so would mean that each RDBMS would have its own transaction. If the first
transaction succeeded but the second transaction failed, the logical data set represented
by the Entity beans would no longer be consistent. The “C” of the ACID test would be
broken.
Another case where only a single transaction is required is when interacting with JMS
queues or topics. You can imagine that a queue might implement a To Do list. A task on
the To Do list might be “invoice customer A for $20,” involving an update to an
RDBMS. If the task is removed from the To Do list as one transaction, and the update to
the RDBMS performed as another transaction, there is again the possibility that the second transaction fails. In other words, the task is removed from the To Do list, but no
invoice is raised.
The two phase commit protocol (more commonly called just 2PC) is the mechanism by
which the transaction manager (EJB container) interacts with each of the resource managers (RDBMS or JMS queues and topics). In the EJB environment, it works as follows
(the numbers correspond to the steps in Figure 8.6):
1. The transaction manager within the EJB container creates a new transaction as
needed. Generally, this will be when a Session bean’s method is invoked.
If the Session bean has been deployed under CMTD, the bean’s proxy will make
this request to the transaction manager. If the Session bean is deployed under
BMTD, the bean itself will effectively make this request.
2. The Session bean interacts with Entity beans. The current transaction will be propagated through to them (assuming they are deployed with Required or Mandatory
value for their trans-attribute element).
3. In turn, the Entity beans interact with the RDBMSs through an XA-compliant
java.sql.Connection. “XA-compliant” means supporting the two phase commit
protocol (or 2PC); more on this shortly. If the Entity bean is BMP, the interaction
with the RDBMS will be done by the bean itself; if the bean is CMP, the interaction will be by the generated subclass. Either way, it amounts to the same thing.
4. Each of the XA-compliant Connections registers itself with the EJB’s transaction
manager. More correctly, a javax.transaction.xa.XAResource representing and
associated with the Connection is registered as part of the current javax.transaction.Transaction.
5. When all method calls to the Entity beans have been made, the Session bean indicates to the transaction manager that the transaction should be committed.
355
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 356
356
Day 8
6. The transaction manager performs the first “prepare” phase of the commit. It iterates over each of the XAResources that constitute the transaction and requests confirmation that they are ready to be committed. In turn, the XAResource just delegates this request to its corresponding XA-compliant java.sql.Connection.
7. When all resources have indicated that they are prepared, the transaction manager
performs the second “commit” phase. It again iterates over each of the
XAResources and requests them to commit.
The JTA API
An XA-compliant java.sql.Connection, as previously described, is one that provides
the ability to return an XAResource for registering with the current transaction. The XA
protocol is an industry standard, defined by the X/Open group, to allow a transactional
resource manager to participate in a global transaction controlled by an external transaction manager. The JTA API is effectively a mapping into Java of this XA protocol.
In fact, the java.sql.Connection interface does not mandate XA-compliance, so the
previous description was a slight simplification. Instead, XA-compliance is provided by
classes and interfaces in the javax.sql package, part of the J2EE platform. Some of the
more relevant classes of java.sql, javax.sql, javax.transaction, and javax.transaction.xa are shown in Figure 8.7.
FIGURE 8.7
The javax.sql and
interface
javax.sql.DataSource
java.sql.DriverManager
getConnection
!
getConnection
javax.transaction
interface
java.sql.Connection
packages together provide support for 2PC
against RDBMSs.
createStatement
prepareStatement
prepareCall
close
!
Some methods and
variables not shown
interface
…ConnectionPoolDataSource
interface
…PooledConnection
getPooledConnection
getConnection
close
interface
javax.sql.XADataSource
getXAConnection
interface
…sql.XAConnection
getXAResource
interface
javax.transaction.UserTransaction
interface
javax.transaction.TransactionManager
begin
commit
rollback
setRollbackOnly
getStatus
setTransactionTimeout
begin
commit
getStatus
getTransaction
resume
rollback
setRollbackOnly
setTransactionTimeout
suspend
interface
javax.transaction.xa.Xid
interface
javax.transaction.Transaction
interface
javax.transaction.xa.XAResource
commit
end
forget
getTransactionTimeout
isSameRM
prepare
recover
rollback
setTransactionTimeout
start
commit
delistResource
enlistResource
getStatus
registerSynchronization
rollback
setRollbackOnly
0..*
Delisted resources
are not removed from
this collection
As you know, the java.sql.Connection interface represents a connection to an
RDBMS. In J2SE applications, java.sql.DriverManager can be used to create a connection. Under J2EE, a javax.sql.DataSource object is used.
11 0672323842 CH08
3/20/02
9:29 AM
Page 357
Transactions and Persistence
In J2EE enterprise applications, reusing connections through some sort of connection
pool is critical to ensuring performance and scalability. Many implementations of
DataSource provide built-in connection pooling. The connection returned by
DataSource.getConnection() is effectively a logical connection temporarily associated
with an underlying physical connection. When the close() method on the logical connection called, the underlying physical connection is not closed but is, instead, returned
to a connection pool.
J2EE also offers another approach for RDBMS vendors to provide connection pooling,
through the javax.sql.ConnectionPoolDataSource interface. This returns
PooledConnections that are physical connections to the database. In turn, they provide a
getConnection() method that returns a logical connection that wraps them, so the final
effect is much the same as before.
Tip
If you want, you can think of the getConnection() method of
PooledConnection as leasing the connection from the pool. The close()
method releases the PooledConnection back into the pool to be used again.
Closely related to ConnectionPoolDataSource is the javax.sql.XADataSource, which
returns javax.sql.XAConnections. This is a sub-interface of PooledConnection, so it
works in the same way, providing the getConnection() to return a logical
java.sql.Connection that wraps it. However, it also provides the getXAResource()
method that returns a javax.transaction.xa.XAResource. Consequently, the
XAConnection acts as a bridge between the resource manager’s notion of connection and
the transaction manager’s notion of resource.
A J2EE-compliant resource manager must be able to support each of these three different
interfaces (XADataSource, ConnectionPoolDataSource, and DataSource
itself).
DataSource
The J2EE RI deploytool can be used to configure XADataSources against Cloudscape
by using the Tools, Server Configuration menu option. Alternatively, the
resource.properties file under %J2EE_HOME%\config can be edited directly. The dialog
box and required entries are shown in Figure 8.8. (You’ll also need to remove the previous definition of jdbc/Agency, listed under DataSources/Standard node of the
Explorer).
357
8
11 0672323842 CH08
3/20/02
9:29 AM
358
Page 358
Day 8
FIGURE 8.8
can be used to
configure
XADataSources.
deploytool
As you saw earlier today, the EJB application’s interface into the EJB container’s transaction manager implementation is solely through the
javax.transaction.UserTransaction interface. Indeed, there is no direct way to access
the javax.transaction.TransactionManager object or the
javax.transaction.Transaction object that corresponds to the UserTransaction
(though obviously they are available to the EJB container itself).
Tip
Some EJB containers make the Transaction and TransactionManager interfaces available—through JNDI. As ever, using these value-add features will
compromise application portability.
Figure 8.9 shows an object instance diagram to indicate (some of) the objects that might
be instantiated to represent the scenario shown back in Figure 8.6.
In practice, there probably would not be two objects for the XAConnection and
XAResource interfaces (oraConn/oraRes and sybConn/sybRes in the diagram). More likely, an RDBMS vendor would implement a concrete class that implements both of these
two interfaces. The JTA API describes such a class as the ResourceAdapter.
Consequently, each transaction managed by the EJB container’s transaction manager will
have a collection of ResourceAdapters, each also being a physical connection to some
resource manager. Indeed, if you have used the Adapter design pattern, you’ll recognize
that the ResourceAdapter is well named, combining two orthogonal interfaces into a single instance.
11 0672323842 CH08
3/20/02
9:29 AM
Page 359
Transactions and Persistence
FIGURE 8.9
359
ctxA:EJBContext
:UserTransaction
An object instance diagram showing
XAConnections and
XAResources.
ctxB:EJBContext
«creates»
delegate
:TransactionManager
proxyA
beanA
«uses»
oraConn:XAConnection
«creates»
:Transaction
oraRes:XAResource
delegate
oraPhysConn:Connection
proxyB
beanB
«uses»
sybConn:XAConnection
sybRes:XAResource
delegate
sybPhysConn:Connection
What If It Goes Wrong?
You’ve seen how the 2PC protocol is intended to work. However, the whole point of 2PC
is to ensure transactional consistency, even in the event of an unexpected failure. So,
what happens when it goes wrong?
There are two cases to deal with. First, it could be that a resource manager enlisted into
the transaction may no longer be available when the application (in an EJB context, the
Session bean or its proxy) decides to commit. It could be that the network has failed
since the original interaction with the Entity bean that represents some data residing on
that resource manager.
In this first case, the prepare phase of the 2PC protocol fails. Because the transaction
manager has been unable to get an acknowledgement within its timeout, it will roll back
the transaction. When the resource manager that failed is restarted, it will (as part of its
so-called recovery process) automatically roll back any work done as a part of the transaction.
In the second case, a resource manager becomes unavailable after it has acknowledged
the prepare, but before the commit phase. This rare case causes more problems because
the transaction manager may already have sent the commit message to some other
resource managers. Nevertheless, the transaction manager will continue to send the commit message to all other resource managers.
When the resource manager that failed is restarted, it will detect that it had acknowledged a prepare. It then contacts the transaction manager to determine whether the transaction was actually committed or was rolled back. It then performs the same (commit or
rollback) as part of its recovery process.
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 360
360
Day 8
The prepare phase is more than the transaction manager checking that all resource managers are still available. It is also possible for resource managers to unilaterally decide to
abort a transaction for some reason. When this happens, the transaction manager will
send a rollback message to all participating resource managers, rather than a commit.
If you have done any JavaBean programming, some of this will be starting
to sound familiar. The java.beans.VetoableChangeSupport class works in a
very similar way.
Tip
In addition to failures of the prepare or the commit phase of the 2PC, there are also occasional cases when a resource manager may take a so-called heuristic decision that could
be in conflict with the semantics of the transaction. For example, a resource manager
could have a policy that, once prepared, it will commit if the transaction manager has not
confirmed the outcome (commit or rollback) within a certain period. One reason that a
resource manager might do this would be to free up resources.
If a heuristic decision is made, it is the responsibility of the resource manager to remember this decision. In an RDBMS, this is often stored in some sort of system table. Put
bluntly, this information is required to allow the administrator to correct any issues with
the data if the heuristic decision went against the transaction’s actual outcome. You might
have noted that the XAResource interface defines a forget() method; this allows the
resource manager to finally forget that a heuristic decision was made.
This probably all sounds pretty arcane, but is needed if you want to understand the full
set of status values defined by the javax.transaction.Status interface. You’ll recall
that a subset of these was presented in Table 8.2. Table 8.3 shows all of the constants.
TABLE 8.3
All of the Constants Defined in javax.transaction.Status
Constant
Meaning
Typical Actions
STATUS_
No transaction is active.
tran.begin()
STATUS_ACTIVE
A transaction is active and can be used.
Use resource manager.
tran.commit() to commit.
tran.rollback() to rollback.
STATUS_MARKED_
A transaction is active, but has been
marked for rollback. Any attempt to
commit it will result in a javax.
tran.rollback()
to start new transaction.
NO_TRANSACTION
ROLLBACK
transaction.RollbackException
being thrown.
11 0672323842 CH08
3/20/02
9:29 AM
Page 361
Transactions and Persistence
TABLE 8.3
361
Continued
Constant
Meaning
Typical Actions
STATUS_PREPARED
A transaction is active, and is in the
process of being committed. The first
“prepare” phase of the 2PC protocol
has completed.
Nothing; wait for transaction to
complete.
STATUS_
A transaction is active, and is in the
process of being committed. The first
“prepare” phase of the 2PC protocol is
in progress.
PREPARING
STATUS_
COMMITTING
STATUS_
ROLLING_BACK
STATUS_
COMMITTED
STATUS_
ROLLEDBACK
STATUS_UNKNOWN
A transaction is active, and is in the
process of being committed. The second
“commit” phase of the 2PC protocol is
in progress.
A transaction is active, and is in the
process of being rolled back.
The previous transaction has committed,
but there is likely to be some heuristic
data available (otherwise, the status
returned would have been
STATUS_NO_TRANSACTION).
Use administrative tool to forget
heuristics after checking data is valid
in resource.
tran.begin() to start new transaction,
but heuristic data may be lost.
The previous transaction has rolled back,
but there is likely to be some heuristic
data available (otherwise, the status
returned would have been
STATUS_NO_TRANSACTION).
This is a transient status. Subsequent
calls will resolve to another status.
Wait.
JTA Versus JTS
There are two Java APIs relating to transactions:
• The Java Transaction API (JTA)—(Already introduced) The JTA classes and interfaces reside in the javax.transaction and javax.transaction.xa packages.
• The Java Transaction Services API (JTS)—This is a Java mapping for the OMG’s
Object Transaction Service v1.1 to interoperate with CORBA ORB/TS standard
interfaces. This includes on-the-wire propagation of transactions over CORBA’s
IIOP network protocol. The JTS classes and interfaces reside in the javax.jts
package.
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 362
362
Day 8
For its part, the JTS specification (downloadable from http://java.sun.com/products/jts/index.html) mandates that any compliant Transaction Manager implementation must also provide complete support for the JTA API, so you can think of JTS as the
“back-end” of JTA. Some Java APIs (such as JDBC and JNDI) use the terms API and
SPI, where API is the application developer’s programming interface, and SPI is supporting the service-provider’s interface. For example, a JDBC driver written by Oracle Corp.
would be an implementation of the JDBC SPI. Using this terminology, JTS is the SPI
portion of JTA.
The EJB specification mandates that the EJB container must provide access to the JTA
API, so some vendors will do this by implementing JTS. Such vendors can then offer
transaction propagation between EJBs that reside on different EJB servers, because both
such servers will effectively appear as CORBA servers implementing OTS. Figure 8.10
shows this.
FIGURE 8.10
JTS support means
transactions can be
propagated between
EJB servers.
local
client
session
bean
entity
bean
Ora
transaction manager
entity
bean
Syb
transaction manager
Obviously, both EJB servers need to support JTS, but they need not be implemented by
the same EJB container vendor. The transaction manager on the called EJB server is
enlisted as a resource of the transaction manager on the calling EJB server.
While the EJB specification mandates JTA support, it does not mandate that JTS be used
to realize this support. In other words, the EJB container is free to provide any implementation of the interfaces in JTA, but it need not support the additional requirements
(principally CORBA interoperability) that make up the JTS API. Some vendors—perhaps
those from a database or a Web application background—may implement JTA entirely
“within” their EJB container and offer no CORBA interoperability services. On the other
hand, the EJB specification also indicates that if an EJB container vendor does elect to
provide transactional propagation, it must do so by supporting OTS.
11 0672323842 CH08
3/20/02
9:29 AM
Page 363
Transactions and Persistence
Caution
In fact, if the vendor elects to support transactional propagation, the EJB
specification requires support for OTS v1.2 (see for example section 19.6.1.1).
Strictly speaking, JTS 1.0 is a Java mapping only for OTS v1.1, so JTS 1.0 support does not in itself fully address the requirements of the EJB specification.
If the area of transaction propagation is of particular interest to you, you
should make sure that your EJB container vendor can clarify its position to
you.
Some EJB container vendors will also be vendors of CORBA products, and so are likely
to implement JTA just by implementing JTS.
Overview of Persistence Technologies
The second main topic for today is to discuss persistence options and look at some of the
Java technologies available in this space. At this point, you might be thinking that this is
a moot point; after all, you learned yesterday and on Day 6 how to develop Entity beans,
so what else is there to address?
In response to that question, consider the following two points. First, you used JDBC to
implement BMP Entity beans on Day 6. However, there are other technologies that may
involve less work and could even lend themselves to code generation, or indeed, support
persistence transparently. Second, many J2EE applications will not use EJBs. There are
many successful J2EE enterprise applications built only with servlets, JSPs, and data
access code. Moreover, commercial EJB containers can be costly to purchase, and this
might also be a consideration for your organization.
The three technologies that you will learn more about are as follows:
• JDBC—This is the most mature of the Java persistence technologies. There have
been multiple versions over the last few years (sometimes renamed along the way),
which can be confusing.
The JDBC API itself is not covered, because it is presumed that you are or have
become familiar with it.
• SQLj—This is actually three specifications that combine Java and SQL, either
client-side or within the RDBMS. Some aspects of SQLj are supported through
JDBC (v2.0 and later).
At the time of writing, SQLj was being developed by a consortium of companies
that includes Sun, Oracle, IBM, and Sybase. You can learn more at
http://www.sqlj.org.
363
8
11 0672323842 CH08
3/20/02
9:29 AM
364
Page 364
Day 8
• Java Data Objects (JDO)—This aims to make persistence transparent, either for
small embedded applications or large scale enterprise applications. This latter
objective means it can either replace or supplement Entity EJBs. (At the time of
writing, this specification was still in draft.)
At the time of writing, JDO was being developed through the Java Community
Process, JSR-000012. You can learn more at http://access1.sun.com/jdo. You
can learn about its relationship with JDBC at
http://java.sun.com/products/jdbc/related.html.
This is by no means a definitive list. Specifically, there is nothing to prevent you from
using a vendor-specific API to persist your data, as is used by most OODBMS vendors.
While OODBMSs are not as mainstream nor as prevalent as RDBMSs, they have many
advocates who argue vociferously that the best place to store objects is in an objectbased database. Examples of OODBMS products include (in no particular order) O2,
Objectivity, ObjectStore, Versant, FastObjects (previously POET), Persistence, Jasmine,
Gemstone, and ozone. From a J2EE perspective, many of the OODBMS vendors have rebranded their products to be EJB application servers, while others have positioned their
technology to provide a simple way to implement persistence (within a BMP Entity
bean).
Yet another alternative is to use an object/relational mapping product. In a sense, these
tools combine the familiarity of RDBMS with the intuitiveness of objects. The information that maps the object instances to the relational schema is usually held in a toolspecific repository of some sort, similar in concept to the information provided in a CMP
Entity bean’s deployment descriptor, although typically much more complex and sophisticated. These O/R mapping products usually have sophisticated caching algorithms (for
example, predictively loading related persistent data) that can radically speed up performance. On the other hand, one downside is that there is a learning curve to effectively
configure and maintain the mapping data; sometimes professional assistance is needed.
There are a number of O/R products around, including (in no particular order) TopLink,
CocoBase, JavaBlend, JRelay, OJB, DBGen, JDX, and ObjectDriver.
Whether you can use any of these technologies may depend on the standards and constraints in your organization. Certainly, the vendors of OODBMS and O/R mapping
products claim substantial decreases in the time taken to develop code.
Note
As you will see shortly, one of the implicit objectives of JDO is to create a
standard Java API for OODBMS and O/R mapping products. This may well
cause these products to be adopted by a wider audience.
11 0672323842 CH08
3/20/02
9:29 AM
Page 365
Transactions and Persistence
On the other hand, another consideration for you is the requirement for EJB container
vendors to support CMP 2.0. You may find that some EJB vendors address this requirement simply by cross-licensing one of the more established O/R mapping tools. This is
arguably the best of both worlds; you have access to a mature O/R mapping technology,
but configured using an industry-standard EJB deployment descriptor.
JDBC
In the beginning, there was JDBC v1.0. Although initially introduced as an addendum to
JDK 1.02, it was standardized as part of JDK 1.1 as the classes and interfaces that make up
the java.sql package. It remains an integral part of J2SE 1.3. In an effort to unravel
JDBC’s family tree, Table 8.4 lists the versions of JDBC, J2SE (previously JDK), and J2EE.
TABLE 8.4
JDBC Versions
JDBC
J2SE (JDK) J2EE
Package
JDBC 1.0
JDK 1.1
java.sql
N/A
Significant Features/Notes
DriverManager, Connection, Statement,
ResultSet
Note: Formalized in JDBC 1.2 Spec.
JDBC 2.1
Core API
J2SE 1.2
JDBC 2.0
Optional
Package
JDBC 3.0
J2EE 1.2
J2EE 1.3
java.sql
J2EE 1.2
J2EE 1.3
javax.sql
Scrollable and updateable ResultSets
Batch updates
SQL1999 data types (BLOB, CLOB, ARRAY,
Structured Type, REF)
Mapping SQL UDTs to Java classes
Direct storing of Java objects
Note: When first introduced, was called the
JDBC 2.0 Core API, not 2.1.
DataSource, ConnectionPoolDataSource,
XADataSource
JNDI support
Rowsets
Note: When first introduced, was called the
JDBC 2.0 Standard Extension API.
J2SE 1.4
J2EE 1.4
java.sql
javax.sql
Unifies JDBC 2.1 Core API and
JDBC 2.0 Optional Package
More thorough support for SQL1999 data
types
DATALINK/URL data type (external data)
Savepoint support
Retrieval of auto-generated keys
Multiple open result sets
Define relationship to Connector architecture
Numerous other minor enhancements
365
8
11 0672323842 CH08
3/20/02
366
9:29 AM
Page 366
Day 8
The JDBC 2.1 Core API is included in J2SE 1.2 and also in J2EE 1.2 and J2EE 1.3 platforms. J2EE 1.2 and J2EE 1.3 also require the JDBC 2.0 Optional Package. If all these
different versions aren’t confusing enough, you may occasionally see documentation that
refers to the “JDBC 2.0 API.” This refers to the combination of (what is now called) the
JDBC 2.1 Core API and JDBC 2.0 Optional Package. So, J2EE 1.2 and 1.3 effectively
mandate the JDBC 2.0 API.
However, sanity is about to break out! At the time of writing, JDBC 3.0 was just coming
out of draft, to be part of J2SE 1.4. This unifies both the Core API and the optional
package. Given than J2SE 1.4 will include JDBC 3.0, it will also implicitly be part of
J2EE 1.4.
One notable feature about JDBC 3.0 is that it consolidates support for SQL1999
advanced data types. Support for these was introduced in JDBC 2.1 Core API, when
SQL1999 was still in draft and was called SQL3. Now that SQL1999 is a ratified standard, JDBC 3.0 has added some features that were, by necessity, previously omitted.
You can learn more about JDBC 3.0 (and download its specification) at
http://java.sun.com/products/jdbc/index.html.
The advanced data types defined by SQL1999 are as follows:
•
BLOBs are binary large objects. These can literally store anything, such as a JPEG
image, a recording, a Java serialized object, and so on. BLOBs are transparently
accessed using SQL Locators. The BLOB data isn’t stored along with the rest of the
row; instead, a pointer is held. While this marginally slows down access, it means
that BLOBs can have very large size limits (2Gb or more).
The JDBC ResultSet.getBlob() method and the Blob interface provide access to
data stored in BLOB columns.
•
CLOBs are character large objects, similar to BLOBs except that the data is treated as
characters; as a result, conversion of data between locales is performed. CLOBs are
also transparently accessed via SQL Locators.
The JDBC ResultSet.getClob() method and the Clob interface provide access to
data stored in CLOB columns.
• SQL structured types are a mechanism to allow user-defined data types to be
defined. They are somewhat similar to a class in Java that has only public fields.
The following SQL1999 command defines a structured type called XY_POINT:
CREATE TYPE XY_POINT AS (X FLOAT, Y FLOAT) NOT FINAL
SQL1999 allows columns to be defined of these types, and also allows tables to be
defined consisting of instances of these types. Defining a table from a structure
type is done using SQL syntax such as the following:
11 0672323842 CH08
3/20/02
9:29 AM
Page 367
Transactions and Persistence
367
CREATE TABLE SCATTER_GRAPH OF XY_POINT (REF OID IS SYSTEM GENERATED);
The OID is an identifier to a row in this table. The JDBC ResultSet.getObject()
method and the Struct interface provide access to data stored in structured type
columns (not structured type tables). To simplify implementation, custom mappings can be defined to convert the data of the SQL structured type to a Java class.
This is somewhat akin to implementing the java.io.Externalizable interface for
the target Java class; java.sql.SQLData interface is the actual interface that must
be implemented.
•
ARRAYs
provide the ability to store vector data. In the case study, the Applicant
table defines two columns—address1 and address2. Using SQL1999 types, these
could instead be defined to be a single ARRAY column. Usually ARRAYs are accessed
via SQL Locators, although it is possible for the data to be stored along with the
rest of the data on the row.
The JDBC ResultSet.getArray() method and the Array interface provide access
to data stored in ARRAY columns.
•
REFs
are a persistent pointer to an instance of a SQL structured type, defined when
a table is created from an SQL structured type. You might think of them as an
SQL1999 equivalent to a foreign key.
The JDBC ResultSet.getRef() method and the Ref interface provide access to
the SQL REF. This reference can be used to access data held in a table defined to
be of a structured type (such as SCATTER_GRAPH). This is the way that data stored in
structured type tables can be accessed.
These data types may be unfamiliar to you; after all, many RDBMS do not support them
yet. However, JDBC 3.0 includes support for these data types because the JDBC specification authors expect RDBMS support for them will have become widespread within the
next five years or so.
That said, there is some overlap in intent between SQL1999 data types and the objectives
of SQLj. If SQLj proves more popular (because it is rooted more on Java technology
rather than SQL), it could be that Java’s popularity may decelerate the adoption of
SQL1999 data types. Only time will tell. So, high time to look at this next technology,
SQLj.
SQLj
The SQLj initiative defines three ways to combine Java and SQL:
• SQLj Part 0 defines a mechanism for embedding SQL calls within Java code. The
SQL is converted into JDBC calls by using a preprocessor.
8
11 0672323842 CH08
3/20/02
9:29 AM
368
Page 368
Day 8
• SQLj Part 1 inverts this, placing Java within SQL. It defines extensions to the SQL
syntax to allow Java static methods to be called either as if they were SQL stored
procedures or as user-defined functions within SQL statements.
• SQLj Part 2 also places Java within SQL, but here defining a mechanism for Java
classes to be used to define SQL types. This allows table columns to be defined as
a type of a Java class; a Java object instance can thus be stored directly in the column.
The SQLj Part 0 has already been adopted as an ANSI and ISO standard (ANSI ref:
X3.135.10-1998, ISO/IEC ref: 9075-10:2000), “Information Technology—Database
Languages—SQL—Part 10: Object Language Bindings (SQL/OLB).” If you see a reference to ANSI SQL Part 10, it just means SQLj Part 0.
The other parts have not yet been adopted by ANSI or ISO/IEC, but are being approved
through NCITS, the (U.S.) National Committee for Information Technology Standards.
Most of the NCITS standardization activities result in national (ANSI) or international
(ISO/IEC) standards. SQLj Part 1 is also known as NCITS 331.1; SQLj Part 2 is NCITS
331.2.
The following sections look at each of these in turn.
SQLj Part 0
SQLj Part 0 is probably the most straightforward for database vendors to implement,
because it an entirely a client-side technology. The developer, rather than writing a regular Java class whose file has a .java suffix, writes a hybrid class instead that has embedded SQL commands within it. Such a class resides in a file with a .sqlj suffix. A preprocessor (SQLj calls it a translator) converts the embedded SQL commands into JDBC
equivalents, resulting in a .java file that can be compiled in the usual way. The sqlj
translator also creates a set of serialized .ser profile files that hold information about the
embedded SQL.
Note
On a historical note, EJB 1.0 used .ser files to hold deployment descriptor
information (as you know, XML files are now used instead). SQLj Part 0 was
initially specified at around the same time as EJB 1.0 (1998 or so), so it is
interesting to speculate if it were being specified now whether it would use
XML files to hold this supplementary information.
After the generated .java and .ser files have been created, they can be compiled and
packaged up into an EJB JAR file in the usual way. The .ser profile files simply need to
be part of the ejb-jar. This overall process is shown in Figure 8.11.
11 0672323842 CH08
3/20/02
9:29 AM
Page 369
Transactions and Persistence
FIGURE 8.11
JobBean.sqlj
SQLj Part 0 uses a
translator to convert
SQLj commands into
JDBC.
sqlj
translator
369
Deployment
Descriptors
Job.java
JobBean.java
javac
complier
.ser
profiles
Job*.class
packager
JobBean.jar
The sqlj translator can optionally also check the embedded SQL against a target database to make sure that it is syntactically correct (that all tables, columns, and so on that
are referenced do exist).
Listing 8.5 shows the BMP version of the ejbLoad() method for the Job Entity bean
(from Day 7), written using JDBC.
LISTING 8.5
JDBC Version of ejbLoad() for BMP Job Bean
1: public void ejbLoad(){
2:
JobPK key= (JobPK)ctx.getPrimaryKey();
3:
Connection con = null;
4:
PreparedStatement stmt = null;
5:
ResultSet rs = null;
6:
try {
7:
con = dataSource.getConnection();
8:
stmt = con.prepareStatement(
9:
“SELECT description,location
➥FROM Job
➥WHERE ref = ?
➥AND customer = ?”);
10:
11:
stmt.setString(1, key.getRef());
12:
stmt.setString(2, key.getCustomer());
13:
rs = stmt.executeQuery();
14:
8
11 0672323842 CH08
3/20/02
9:29 AM
Page 370
370
Day 8
LISTING 8.5
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: }
Continued
if (!rs.next()) {
error(“No data found in ejbLoad for “+key,null);
}
this.ref = key.getRef();
this.customer = key.getCustomer();
this.customerObj =
➥customerHome.findByPrimaryKey(this.customer); // derived
this.description = rs.getString(1);
String locationName = rs.getString(2);
this.location =
➥(locationName != null)?
➥locationHome.findByPrimaryKey(locationName):null;
// load skills
stmt = con.prepareStatement(
“SELECT job, customer, skill
➥FROM JobSkill
➥WHERE job = ?
➥AND customer = ?
➥ORDER BY skill”);
stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
rs = stmt.executeQuery();
List skillNameList = new ArrayList();
while (rs.next()) {
skillNameList.add(rs.getString(3));
}
this.skills = skillHome.lookup(skillNameList);
}
catch (SQLException e) {
error(“Error in ejbLoad for “+key,e);
}
catch (FinderException e) {
error(“Error in ejbLoad (invalid customer or location) for “+
➥key,e);
}
finally {
closeConnection(con, stmt, rs);
}
Listing 8.6 shows the same method, implemented using SQLj embedded SQL syntax.
11 0672323842 CH08
3/20/02
9:29 AM
Page 371
Transactions and Persistence
LISTING 8.6
371
SQLj Version of ejbLoad() for BMP Job Bean
1: // assuming
2: // import sqlj.runtime.*;
3:
4: public void ejbLoad(){
5:
JobPK key= (JobPK)ctx.getPrimaryKey();
6:
7:
#sql context CustomConnectionContext;
8:
CustomConnectionContext conCtx;
9:
try {
10:
con = dataSource.getConnection();
11:
conCtx = new CustomConnectionContext(con);
12:
#sql [conCtx]
13:
{ SELECT description,location
14:
INTO :this.description, :this.locationName
15:
FROM Job WHERE ref = :(key.getRef())
16:
AND customer = :(key.getCustomer())
};
17:
18:
this.ref = key.getRef();
19:
this.customer = key.getCustomer();
20:
this.customerObj =
➥customerHome.findByPrimaryKey(this.customer); // derived
21:
this.location =
➥(locationName != null)?
➥locationHome.findByPrimaryKey(locationName):null;
22:
23:
// load skills
24:
#sql iterator SkillIterator
➥(String job, String customer, String skill);
25:
SkillIterator skillIter =
26:
#sql [conCtx]
27:
{ SELECT job, customer, skill
28:
FROM JobSkill
29:
WHERE job = :this.ref
30:
AND
customer = :(customerObj.getLogin())
31:
ORDER BY skill };
32:
33:
List skillNameList = new ArrayList();
34:
while (skillIter.next()) {
35:
skillNameList.add(skillIter.skill());
36:
}
37:
skillIter.close();
38:
39:
this.skills = skillHome.lookup(skillNameList);
40:
}
41:
catch (SQLException e) {
42:
error(“Error in ejbLoad for “+key,e);
43:
}
44:
catch (FinderException e) {
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 372
372
Day 8
LISTING 8.6
45:
46:
47:
48:
49:
50:
51: }
Continued
error(“Error in ejbLoad (invalid customer or location) for “+
➥key,e);
}
finally {
conCtx.close(ConnectionContext.KEEP_CONNECTION);
closeConnection(con, null, null);
}
You can see that SQLj binds host variables (that is, Java instance or local variables) or
return values of expressions to the SQL statement using a : prefix. You can see this in
the first SQL statement (loading from the Job table) where two function expressions are
used in the WHERE clause. Host variables can also be written to through the INTO clause.
Again, in the first SQL statement, the INTO clause is used to populate the location and
description host variables. Contrast the use of the INTO clause with the JDBC equivalent that has to iterate over a java.sql.ResultSet.
The second SQL statement is a query against the JobSkill table, and multiple rows are
expected this time. A typesafe iterator, SkillIterator, is defined and is used to traverse
the returned rows.
Connection information can be specified in SQLj in a variety of ways. The normal
approach is to embed the connection information into the aforementioned .ser profile
files, replacing the J2SE java.sql.DriverManager approach. Obviously, this isn’t
appropriate in a J2EE environment, because only logical connections should be acquired
via javax.sql.DataSource.
One solution is to use an explicit connection context to define the connection that the
SQL will be executed under. In Listing 8.6, the line
#sql context CustomConnectionContext;
defines CustomConnectionContext as a user-defined class, extending
sqlj.runtime.ConnectionContext and with a constructor accepting a
java.sql.Connection. The line
CustomConnectionContext conCtx;
defines a reference conCtx of this class, and
con = dataSource.getConnection();
conCtx = new CustomConnectionContext(con);
instantiates an object of this class. The conCtx is used within the subsequent #sql calls.
11 0672323842 CH08
3/20/02
9:30 AM
Page 373
Transactions and Persistence
You may find that your vendor’s SQLj implementation offers the ability to make a
CustomConnectionContext the default context. Part of the SQLj Part 0 specification
includes the concept of customizing profiles to a specific runtime environment, similar to
the notion of adding the auxiliary deployment descriptor for EJBs. Your customizer may
allow you to indicate that the default ConnectionContext is one that is “J2EE-aware.”
This would avoid the need for any of the connection context code given in Listing 8.6.
SQLj Part 1
Whereas SQLj Part 0 embedded SQL within Java, the SQLj Part 1 specification inverts
this and puts the Java within SQL. More properly, it allows Java static methods to be
called as if they were SQL stored procedures. It also allows Java static methods to act as
user-defined functions called from within SQL statements (such as SELECT or UPDATE).
What this means is that, whereas SQLj Part 0 addresses Java and SQL for client-side
code, SQLj Part 1 puts Java and SQL together in the server. Put another way, SQLj Part
1 can only be supported by RDBMS vendors that provide the capability to install this
functionality.
Note
There are two main mechanisms by which RDBMS vendors can support SQLj
Part 1. Many vendors have long provided the ability to hook user-defined
functions into the RDBMS, where those functions are written using C or
some other 3GL. Some such vendors offer SQLj Part 1 support by converting
the Java static methods into corresponding C code and then uploading.
Alternatively, the RDBMS vendor can actually implement a JVM that runs
within the RDBMS itself. When the Java static method is called, the SQL
“engine” within the RDBMS passes over control to the JVM to evaluate the
result.
As an example, consider the Applicant table from the case study. This table records
each applicant’s address in the address1 and address2 columns. For the sake of illustration, imagine that it also has the state, zip, and country columns.
If the Job agency users want to do a mailshot, they will need to print some labels. The
labels need to combine the information of the name, address1, address2, state, zip,
and country columns, with a newline between each, also dealing with the case where
some of these columns are null. Listing 8.7 defines a utility class that will format the
label string appropriately.
373
8
11 0672323842 CH08
3/20/02
374
9:30 AM
Page 374
Day 8
LISTING 8.7
Static Methods Can Be Invoked as SQL UDFs
1: public class Utils {
2:
public static String labelString(
3:
String name, String address1, String address2,
4
String state, String zip, String country) {
5:
StringBuffer label = new StringBuffer(name);
6:
if (address1 != null) {
7:
label.append(“\n”); label.append(address1);
8:
}
9:
if (address2 != null) {
10:
label.append(“\n”); label.append(address2);
11:
}
12:
if (state
!= null) {
13:
label.append(“\n”); label.append(state
);
14:
}
15:
if (zip
!= null) {
16:
label.append(“\n”); label.append(zip
);
17:
}
18:
if (country != null) {
19:
label.append(“\n”); label.append(country );
20:
}
21:
return label.toString();
22:
}
23: }
This class would be compiled as usual and bundled up into a JAR file, say utils.jar.
SQLj defines the following mechanism for installing classes as JAR files:
sqlj.install_jar (‘file:utils.jar’, utils_jar’ )
This gives the name utils_jar to the JAR file utils.jar. Each RDBMS vendor will
provide a tool that implements this mechanism.
With the code installed, the following defines the user-defined function label_for to be
the Utils.labelString() method:
create function label_for(
name varchar, address1 varchar, address2 varchar,
state char(2), zip char(9) country varchar)
returns varchar
language java
parameter style java
external name ‘utils_jar:Utils.labelString’;
Finally, the following SQL can be executed:
SELECT label_for(name, address1, address2, state, zip, country)
FROM Applicant
11 0672323842 CH08
3/20/02
9:30 AM
Page 375
Transactions and Persistence
Using a function alias means that access to this function can be granted or revoked as
needed by using the SQL grant execute command. Additionally, SQLj specifies that
security can be associated with the actual JAR file using grant usage on utils_jar
to some_user.
Some RDBMS vendors have relaxed the requirement to define function aliases and simply allow the Java static method to be called directly:
SELECT Utils.labelString(name, address1, address2,
state, zip, country) AS label
FROM Applicant
Note
One of the major RDBMS vendors (Sybase) allows Java UDFs to read/write
BLOB (image) and CLOB (text) columns as java.io.InputStream and
java.io.Readers, respectively. So, given a table of
create table letter
( letter_id int,
letter_text text )
and a static method such as
public class TextUtils {
public static int length(java.io.Reader reader) {
int size = 0, read;
char[] buf = new char[1024];
while( (read = reader.read(buf)) != -1) {
size += read;
}
return size;
}
}
then SQL such as the following can be submitted:
SELECT letter_id, TextUtils.length(text)
FROM
letters
Clearly, this opens up a lot of possibilities. Rather than just counting the
number of characters in the CLOB column, the Java method could search,
apply regular expressions, run AWK scripts, or parse XML.
Indeed, the RDBMS vendor has developed an XML query engine implemented in precisely this fashion. As a result, developers can store XML files as
CLOBs and process them within the RDBMS server itself.
SQLj Part 1 also allows stored procedures to be defined as an alias for Java static methods. So, a Java client making a JDBC call such as the following
CallableStatement stmt = con.prepareCall(“{ call some_such_procedure ?, ?}”;
stmt.execute();
375
8
11 0672323842 CH08
3/20/02
376
9:30 AM
Page 376
Day 8
is actually invoking a Java static method aliased to some_such_procedure. This is almost
like performing a Java RMI call, except over a database network connection.
Such “Java stored procedures” can (somewhat arbitrarily) be split into two types—those
that have embedded SQL calls implemented using JDBC or SQLj and those that do not.
Java stored procedures that do not have embedded SQL often perform some sort of complex computation that is not easily expressed in SQL. As a somewhat contrived example,
Listing 8.8 shows a reworked version of the Utils.labelString method that allows it to
be called as a stored procedure.
LISTING 8.8
Static Methods Can Be Invoked as SQL Stored Procedures
1: public class Utils {
2:
public static String labelString(
3:
String name, String address1, String address2,
4:
String state, String zip, String country,
5:
String[] returnLabel) {
6:
StringBuffer label = new StringBuffer(name);
7:
if (address1 != null) {
8:
label.append(“\n”); label.append(address1);
9:
}
10:
if (address2 != null) {
11:
label.append(“\n”); label.append(address2);
12:
}
13:
if (state
!= null) {
14:
label.append(“\n”); label.append(state
);
15:
}
16:
if (zip
!= null) {
17:
label.append(“\n”); label.append(zip
);
18:
}
19:
if (country != null) {
20:
label.append(“\n”); label.append(country );
21:
}
22:
returnLabel[0] = label.toString();
23:
}
24:
// code omitted
25: }
The stored procedure definition for this method is as follows:
create procedure label_it
(in name varchar, in address1 varchar, in address2 varchar,
in state char(2), in zip char(9), in country varchar,
out returnLabel varchar)
language java
parameter style java
external name ‘utils_jar;Utils.labelString’;
11 0672323842 CH08
3/20/02
9:30 AM
Page 377
Transactions and Persistence
377
The following code will work to invoke this stored procedure from JDBC:
CallableStatement stmt = con.prepareCall (
➥”{call label_it ?, ?, ?, ?, ?, ?, ? }”);
stmt.setParameter(1, someName);
stmt.setParameter(2, someAddress1);
stmt.setParameter(3, someAddress2);
stmt.setParameter(4, someState);
stmt.setParameter(5, someZip);
stmt.setParameter(6, someCountry);
String theReturnedLabel;
stmt.registerOutParameter(7, Types.VARCHAR );
stmt.setParameter(7, theReturnedLabel);
stmt.execute ();
You may have noticed in Listing 8.8 the peculiar fact that the output parameter,
returnLabel, is defined to be an array of Strings, rather than just a String. This is no
mistake; the method needs to be able to change the value of the parameter. The array
passed in has precisely one element, the initial value of the returnLabel, and can be
changed.
Tip
If you are a C or C++ programmer, you might recognize this as the Java
equivalent of “pointer to a pointer.”
You can probably think of some less contrived examples yourself.
The other type of Java stored procedure is that which does have embedded SQL. Here,
you get the peculiar situation of a Java client calling SQL, which maps to Java calling
SQL. This brings to mind concentric rings of Java and SQL, as shown in Figure 8.12.
FIGURE 8.12
SQL embedded within
Java, embedded
within SQL, embedded
within Java…
Java client
SQL Stored Proc
Java static method
SQL
8
11 0672323842 CH08
3/20/02
9:30 AM
378
Page 378
Day 8
The outermost ring is a Java client (Session bean, CMP Entity bean, servlet, and so on).
The inner three rings are server-side, running within the RDBMS. The Java client
invokes an SQL stored procedure, in turn aliased to a Java static method that then makes
SQL calls.
For the Java stored procedure to query the database, it must have a JDBC connection.
SQLj defines the URL string of jdbc:default:connection to be used as the parameter
to java.sql.DriverManager.getConnection().
Not only can Java static methods query the database, they can also return the results.
This is done by allowing ResultSets to be returned as parameters, in a manner similar to
the returnLabel that you saw earlier.
This second style of Java stored procedure is really just the re-location of a client-side
application. In other words, with appropriate modifications to the mechanism to obtain
the connection, the code could equally run in a regular Java client. Put another way, there
is no difference in the amount of JDBC or embedded SQL code that must be written. As
a result, it can seem that as a technology, this is a curiosity—nothing more. However,
that would be overlooking the important point that running Java client-side code within
the RDBMS itself means that there is no network traffic. Many batch processing jobs
involve downloading large chunks of data, performing complex processing, and then
uploading the results. Using Java stored procedures, all of this work can be done without
incurring any costly network traffic at all.
By making Java static methods executable within the RDBMS, SQLj Part 1 offers some
quite powerful functionality. It is a wonderful demonstration of Java’s oft-quoted “write
once, run anywhere” ability.
SQLj Part 2
The last part of SQLj is possibly the most interesting of all. SQLj Part 2 allows Java
classes to be defined as SQL abstract data types.
You will probably recall from the earlier discussion on SQL1999 advanced data types
that there is overlap between SQL1999 and SQLj Part 2. The most obvious overlap is
that Java classes can be used instead of SQL structured types.
Listing 8.9 shows a class that represents all the states of a Job bean. This includes also
the (names of the) job’s skills and the (name of the) job’s location.
11 0672323842 CH08
3/20/02
9:30 AM
Page 379
Transactions and Persistence
LISTING 8.9
379
The JobData Class Represents All the State of a Job Bean
1: package data;
2: public class JobData implements java.io.Serializable {
3:
public final static long serialVersionUID = 1;
4:
private String ref, customer, description, locationName;
5:
private String[] skillNames;
6:
public JobData(String ref, String customer,
➥String description,
➥String locationName,
➥String[] skillNames) {
7:
this.ref = ref;
8:
this.customer = customer;
9:
this.description = description;
10:
this.locationName = locationName;
11:
if (skillNames = null) { skillNames = new String[0]; }
12:
this.skillNames = new String[skillNames.length];
13:
System.arraycopy(skillNames, 0,
➥this.skillNames, 0, skillNames.length);
14:
}
15:
public String getRef() { return ref; }
16:
public String getCustomer() { return customer; }
17:
public String getDescription() { return description; }
18:
public String getLocationName() { return locationName; }
19:
public String[] getSkillNames() {
20:
String[] skillNames = new String[this.skillNames.length];
21:
System.arraycopy(this.skillNames, 0,
➥skillNames, 0, skillNames.length);
22:
return skillNames;
23:
}
24:
public int getNumberOfSkills() { return this.skillNames.length; }
25: }
The class implements java.io.Serializable and defines serialVersionUID to provide
forward compatibility with future versions of the class.
This class can be bundled into a JAR and installed into the RDBMS, just as for SQLj
Part 1. This time though, a user-defined type can be created from the Java class, using an
extension of the SQL1999 CREATE TYPE syntax:
CREATE TYPE JobType EXTERNAL NAME ‘data.JobData’ LANGUAGE java;
This example is incomplete because SQLj Part 2 is still in development. Some RDBMS
vendors that have supported SQLj Part 2 have used alternative approaches. For example,
the Cloudscape RDBMS uses the following syntax:
CREATE CLASS ALIAS JobType FOR data.JobData;
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 380
380
Day 8
Even then, this is an optional step in Cloudscape, as it is for the Sybase RDBMS. That is,
the fully-qualified Java classname can just be used directly as a column type.
Consequently, the following could be used to define the Job table:
CREATE TABLE Job
(
job data.JobData
)
Each row in the table will hold a serialized instance of the data.JobData class.
Performance-wise, this would be prohibitively expensive to access unless the RDBMS
vendor has implemented a JVM within the RDBMS. If they have done this though, the
cost of deserializing and serializing the objects is not expensive. The RDBMS has suddenly become an ORDBMS (object/relational database).
When the data in the table is actually a Java object, suddenly SQL queries become a lot
more exciting:
SELECT job>>getCustomer(), avg( job>>getNumberOfSkills() )
FROM Job
GROUP BY job>>getCustomer()
This query will find the average number of skills needed for all of the jobs placed by a
customer.
Note
In SQLj Part 2, the >> operator replaces the conventional . notation.
The following query also works:
SELECT job
FROM Job
WHERE job>>getNumberOfSkills() > 2
The calling client’s JDBC is simplicity itself:
ResultSet rs = stmt.execute(
➥”SELECT job FROM Job WHERE job>>getNumberOfSkills() > 2”);
while (rs.next()) {
JobData job = (JobData)rs.getObject(1);
}
11 0672323842 CH08
3/20/02
9:30 AM
Page 381
Transactions and Persistence
Tip
Although this is slightly off-topic, it is worth noting that SQLj Part 2 transparently supports super- and sub-types. One could define a subclass of
JobData and store instances of it within the Job table. Moreover, any overridden methods would be called polymorphically. This feature alone could
radically simplify many database schemas.
Turning back to the SQL1999 data types, you can probably see that Java classes can be
used in lieu of a BLOB, CLOB, or ARRAY, as well as be replaced SQL structured types (for
columns). The following is another definition of the Job table, this time showing skills
as a vector attribute:
CREATE TABLE Job
(
customer VARCHAR,
ref VARCHAR,
description VARCHAR,
location VARCHAR,
skills java.util.List
)
One could then imagine the following query:
SELECT customer, ref
FROM Job
WHERE skills>>contains(“Cigar trimmer”)
Note
The Cloudscape RDBMS goes even further, providing implicit mapping of
regular SQL types (such as VARCHAR) to their Java equivalents (such as
java.lang.String). Hence, the following query would be valid within
Cloudscape:
SELECT customer>>substring(0,2)>>concat(ref)>>length()
FROM Job
WHERE customer>>startsWith(“XYZ”)
Of the SQL1999 data types, only the REF data type has no direct equivalent in SQLj.
Indeed, if you go back to the first alternate definition of the Job table (defined in terms
of the data.JobData class), you can see that the job object has the name of the location,
not a reference to the location. If one were to put this in EJB terms, one might say that
JobData holds the primary key to the job’s location, not a reference to LocationLocal.
381
8
11 0672323842 CH08
3/20/02
382
9:30 AM
Page 382
Day 8
There are some other issues that you should be aware of:
• First is that, given the actual data is held as a Java serialized object, non-Java database clients will not be able to read that data.
Possible fixes for this issue are to
• Write code to read the (well-defined) Java serialization stream
• Implement java.io.Externalizable and provide custom methods to write
the state in some other format (XML, perhaps?)
• Second, the RDBMS’ query optimizer cannot index on the return value of method
calls. For example, looking back at the WHERE clause of one of the previous examples (WHERE job>>getNumberOfSkills() > 2 ), this can only be performed by
deserializing every object and invoking the getNumberOfSkills() method.
One possible fix is to redundantly store the required information in a regular column, and maintain it using triggers. The query then uses this derived column.
Note
True OODBMS do allow indexes to be defined on the return values of readonly methods.
• Last, this is new technology, so it will need to mature before organizations are
ready to trust their valuable data to it.
How does SQLj (parts 1 and 2) fit with EJB and the J2EE platform? Well, the support in
SQLj Part 2 certainly makes implementing BMP Entity beans pretty straightforward,
provided care is taken with relationships:
• If a BMP Entity bean is the parent of a one-to-many composite association, there is
nothing to prevent that bean from simply persisting the dependent child data and
dispensing with the child table in the RDBMS.
• Many-to-one associations, where the bean is a child, are more complex. In general,
only the foreign key value should be persisted in the bean. This does incur extra
cost for looking up the actual parent data. (There is an exception to this, in the special case where the parent is immutable. Then, the parent’s data can safely be
stored in the bean itself.)
• Many-to-many associations can be treated like one-to-many associations, but only
if the data that is held is the foreign key value to the other table, and also provided
that the many-to-many link does not need to be traversed in both ways.
11 0672323842 CH08
3/20/02
9:30 AM
Page 383
Transactions and Persistence
One of the previous examples stored the job skills as a java.util.List of
Strings, which seems elegant, but to identify which jobs require the “Cigar trimmer” skill necessitates instantiating every job instance; in other words, always
coming at the Job/Skill association from the Job side.
Even with these provisos, one could imagine that it would be relatively easy (given a
modern IDE or UML modeling tool) to automatically generate SQLj implementation
code for BMP Entity beans.
On the other hand, the ability to deploy Java code that runs within the RDBMS itself
(using either SQLj Part 1 or Part 2) somewhat muddies the architectural water. You are
by now intimately familiar with the n-tier architecture model. But, if Java can run in the
RDBMS, why need do you have a middle tier at all? This is a question that you are probably best advised to answer yourself. You may take the view that as long as you know
which logical layer your Java code belongs to, it may not matter which physical tier
(Web server, EJB application server, Java-enabled RDBMS server) you choose to deploy
it to.
JDO
The last persistence technology that you will be looking at, Java Data Objects (JDO),
aims high and it aims low. That is, it is intended both for use within embedded devices
(J2ME) but also for use within J2EE environments. In a J2EE environment, JDO can
either supplement or supplant Entity EJBs; if supplementing Entity EJBs, the application
of JDO may be hidden if CMP is being employed (that is, the EJB vendor uses JDO but
the bean provider is unaware of this), or it can be used by the bean provider directly if
BMP is in use.
JDO is the most recent of the Java persistent technologies and, at the time of writing, its
specification was still in draft with only an incomplete reference implementation.
Moreover, as a technology it seems to be running in parallel with the various Java platform editions (J2ME, J2SE, and J2EE), and there appear to be no clear indications within
which platform it will eventually reside. Nevertheless, JDO has raised some significant
interest as an API, especially for vendors of OODBMS and O/R mapping tools. Indeed,
the JDO expert group includes representatives from Versant, Poet, Object Design, and
Gemstone, among others. Because JDO completely hides the details of the data store
internals, it is also suitable to access ERP systems; some ERP vendors (such as SAP)
have committed to adopting it.
383
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 384
384
Day 8
Tip
One way to think of JDO is as a vendor-neutral Java API to OODBMS and
O/R mapping tools, just as JDBC provides a vendor-neutral Java API to
RDBMS.
The overriding objective of JDO is to provide persistence transparency. In other words,
the objects that are to be persisted—so-called persistent-capable objects—do not need to
include any logic to make them persistent. Contrast this with EJB Entity beans that need
to implement ejbLoad() and ejbStore(), or with EJB Session beans (and for that matter
servlets) that require reams of JDBC or SQLj calls. The only classes that need to use the
JDO interfaces and classes are those that manage the lifecycle of persistent objects.
As an example of transparent persistence, consider that persistent-capable objects can
also have references to other objects. Assuming that the field that holds this reference has
been marked as being persistent, these referenced classes will then also be made persistent. This is sometimes called “persistence by reachability.” Again, contrast this to EJB,
where a relationship between beans required complex configuration in the EJB deployment descriptor, moreover being constrained to relate through the EJB’s local interface.
Note
It is perhaps a little misleading to claim that JDO does not need relationships between classes to be defined. Rather, it is not within the scope of JDO
to be concerned about this. A JDO implementation provided by an OODBMS
vendor will work in a different way than one provided by an O/R mapping
tool vendor. Any mapping or other configuration information that might
need to be done is performed entirely with vendor-specific tools.
Earlier, JDO was compared to JDBC. JDBC provides a standard API, but leaves
RDBMS vendors at liberty to implement their own data store and network
protocols. Equally, JDO provides just an API and does not get involved in the
internals. This makes JDO applications portable across JDO implementations,
but obviously requires any vendor-specific configuration to be re-applied.
JDO Concepts
JDO’s approach to object persistence revolves around the concept of a cache. This cache
belongs to a client, rather than the server; the objects in the cache are not shared among
all clients. In a J2EE environment, a stateful Session bean would usually be a client;
each active Session bean would have its own cache. At any given time, the cache that
holds the objects is associated with at most one connection and at most one transaction.
11 0672323842 CH08
3/20/02
9:30 AM
Page 385
Transactions and Persistence
Over time, that cache can be used with different transactions, and, for that matter, with
different connections.
The JDO interface that controls the client’s cache is javax.jdo.PersistenceManager. In
a J2EE environment, an instance of this interface is obtained from a
javax.jdo.PersistenceManagerFactory that, in turn, is obtained via JNDI. The JDO
specification describes how the J2EE Connector architecture is used to actually configure
a PersistenceManagerFactory into JNDI; you’ll be learning something about this on
Day 19, “Integrating with External Resources.” All you need to appreciate for now is that
a JDO vendor (an OODBMS vendor, O/R mapping tool vendor, or ERP vendor) will
have implemented the appropriate J2EE Connector interfaces such that a
PersistenceManagerFactory will be available for you to look up.
Note
JDO also integrates with XA, meaning that distributed transactions across
multiple JDO implementations, and indeed RDBMS data stores, are
supported.
JDO defines two main ways in which the cache can be used. All JDO implementations
must support so-called data store transactions and can optionally support optimistic
transactions:
• When a persistent-capable object is in a cache through a data store transaction, it
effectively prevents any other user from holding this object in his or her cache.
It is almost as if the object is “checked out” to the user’s cache, a little like checking out code from a source code control system. It remains there for that user’s
exclusive use (to be read or modified) until he or she indicates that the transaction
is complete. Any other user who wants to use the object must wait until the original user’s transaction has completed.
You may perhaps recognize this approach; it is often called pessimistic locking.
• Although not mandatory within the JDO specification, most JDO implementations
suitable for use within a J2EE environment will also support optimistic locking.
Here, it is possible for a persistent-capable object to reside in two different users’
caches at the same time. As long as each only reads the data from the object, there
are no issues (hence, “optimistic”).
If one user modifies the data and commits their optimistic transaction, the new
state of the object will be transparently written back to the data store. The other
user does not necessarily know about this (though he or she can request to refresh
the object instance if needed).
385
8
11 0672323842 CH08
386
3/20/02
9:30 AM
Page 386
Day 8
Of course, issues do arise when both users modify the data. In this case, the first
user to commit his or her transaction will succeed. When the second user attempts
to commit his or her transaction, the PersistenceManager will throw a
javax.jdo.JDOUserException, detailing that the state of the object has been
changed by some other user since it was first instantiated in the cache.
From the data store’s perspective, the optimistic transaction approach actually involves
two data store transactions. The first is short-lived, lasting long enough just to read the
data from the data store. The second will happen some time later (perhaps seconds, minutes, or longer) and again will be short-lived. During this second data store transaction,
the persistent data will be updated with the modified data taken from the committing
user’s cache.
The JDO specification also defines the notion of JDO identity. There are three different
ways for a JDO vendor to implement JDO identity, depending on the underlying technology:
• For JDO implementations based on an O/R mapping tool, JDO identity basically
corresponds to the primary key as defined in the RDBMS. The JDO specification
terms this application identity or primary key identity.
• For JDO implementations based on an OODBMS, JDO identity corresponds to the
object ID as assigned by the OODBMS itself. The JDO specification calls this data
store identity.
• The final JDO identity type is perhaps likely to be least often used; it is simply
called non-data store identity. This relates to objects unique within the JVM, but
that are only ever written to (not read from) a data store (entries in a log file, for
example).
The term application (primary key) identity is used for RDBMS-based identity because it
is effectively the application itself that defines the semantics of the primary key. This has
echoes in EJB where you, as the bean developer, are required to implement a primary
key class or identify the primary key field. The RDBMS data store is expected to enforce
the notion of identity through the use of unique indexes.
JDO identity is not the same as Java object identity, because in a single JVM, there could
be many active PersistenceManagers; if optimistic transactions are used, multiple Java
objects could be instantiated all with the same JDO identity. However, JDO identity does
have some correspondence to Java’s notion of equality (that is, where two objects are
considered the same if equals() returns true). In particular, JDO requires that persistence-capable objects implement equals() in such a way that it returns true if and only
if the JDO identity is the same. Unsurprisingly, this is exactly the requirement that EJB
imposes on primary keys.
11 0672323842 CH08
3/20/02
9:30 AM
Page 387
Transactions and Persistence
Note
JDO is very clear about identity, recognizing that more than one object
instance may be instantiated for a single instance in the data store. In contrast, EJB is quite casual in this regard. Indeed, the EJB specification explicitly
states that EJB containers have latitude to, for example, implement precisely
one Entity bean per instance in the data store (option A, section 10.5.9) or
to have multiple instances (option C).
Such latitude is surprising. Option A effectively means that Entity beans
reside in a server-side cache. One consequence of this is that deadlocks must
be handled in the EJB container. Meanwhile, option C effectively means that
Entity beans reside in a client-side cache; any deadlocks arising are handled
in the data store.
For any given persistence-capable object, only one of these different types of JDO identity applies. The JDO deployment descriptor (described briefly later) identifies the type of
JDO identity in use.
javax.jdo Classes and Interfaces
The JDO API is defined by classes and interfaces in the javax.jdo package. Some of the
interfaces are intended to be implemented by the JDO vendor and some by the application developer (but more on this in a moment). Figure 8.13 shows the main classes of the
javax.jdo package.
The javax.jdo.PersistenceManagerFactory, javax.jdo.PersistenceManager,
javax.jdo.StateManager, and javax.jdo.Transaction interfaces are all implemented
by the JDO vendor. The PersistenceManagerFactory has already been discussed. The
PersistenceManager is the most important interface, because it provides the methods to
control the lifecycle of the persistence-capable objects. Relating this back to EJB, you
might think of it as combining the functions of the EJB container and of an EJB’s home
interface. The PersistenceManager also provides access to the current
javax.jdo.Transaction. This allows the application developer to demarcate the transaction boundaries. In a J2EE environment, a CMTD EJB does not need to call the methods
of this interface because the EJB container will do this work. If a BMTD Session bean is
being developed to use JDO, the bean developer can use either the
javax.jdo.Transaction or the javax.transaction.UserTransaction interface. The
former is usually to be preferred however, because this allows a single
PersistenceManager to be used across multiple transactions. If a UserTransaction is
used, a PersistenceManager must be acquired for each and every transaction. Finally,
the StateManager is used internally by the JDO implementation to keep track of changes
to persistence-capable objects.
387
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 388
388
Day 8
FIGURE 8.13
The significant classes
and interfaces of the
javax.jdo package.
interface
…PersistenceManagerFactor
getPersistenceManager
setConnectionFactory
getConnectionFactory
setMultithreaded
getMultithreaded
setOptimistic
getOptimistic
setRetainValues
getRetainValues
setNontransactionalRead
getNontransactionalRead
setNontransactionalWrite
getNontransactionalWrite
setIgnoreCache
getIgnoreCache
getMaxPool
setMaxPool
getMinPool
setMinPool
getMsWait
setMsWait
getProperties
supportedOptions
com.vendor.PMFactoryImpl
interface
…jdo.PersistenceManager
interface
javax.jdo.StateManager
isClosed
close
currentTransaction
evict
evictAll
refresh
refreshAll
newQuery
getExtent
newSCOInstance
newCollectionInstance
newMapInstance
getObjectById
getObjectId
getTransactionalObjectId
makePersistent
makePersistentAll
deletePersistent
deletePersistentAll
makeTransient
makeTransientAll
makeTransientAll
makeTransactional
makeTransactionalAll
makeNontransactional
makeNontransactionalAll
setUserObject
getUserObject
getPersistenceManagerFactory
getObjectIdClass
setMultithreaded
getMultithreaded
setIgnoreCache
getIgnoreCache
interface
…jdo.InstanceCallbacks
jdoPostLoad
jdoPreStore
jdoPreClear
jdoPreDelete
com.vendor.SMImpl
interface
…jdo.PersistenceCapable
READ_WRITE_OK
LOAD_REQUIRED
READ_OK
CHECK_WRITE
MEDIATE_WRITE
CHECK_READ_WRITE
MEDIATE_READ_WRITE
interface
javax.jdo.Transaction
begin
commit
rollback
isActive
setNontransactionalRead
getNontransactionalRead
setNontransactionalWrite
getNontransactionalWrite
setRetainValues
getRetainValues
setOptimistic
getOptimistic
setSynchronization
getSynchronization
getPersistenceManager
com.vendor.TranImpl
«creates»
com.vendor.PMImpl
1
1
0..*
com.mycompany.ApplicationObject
jdoGetPersistenceManager
jdoReplaceStateManager
jdoProvideField
jdoProvideFields
jdoReplaceField
jdoReplaceFields
jdoReplaceFlags
jdoCopyFields
jdoMakeDirty
jdoGetObjectId
jdoGetTransactionalObjectId
jdoIsDirty
jdoIsTransactional
jdoIsPersistent
jdoIsNew
jdoIsDeleted
jdoNewInstance
jdoNewInstance
jdoNewObjectIdInstance
jdoCopyKeyFieldsToObjectId
jdoCopyKeyFieldsToObjectId
+ObjectIdFieldManager
com.mycompany.PersistenceAwareAppObject
Of the two remaining interfaces shown in Figure 8.13, the
javax.jdo.PersistenceCapable interface is implemented by the application developer.
Hang on though! Isn’t JDO meant to support transparent persistence? Doesn’t that mean
that there should be no requirement for the application classes to implement any sort of
interface at all? Well, yes…and no. It is true that the persistence-capable classes developed by the application developer neither need to call the classes in javax.jdo, nor do
they need to implement any of the interfaces in javax.jdo. However, before a persistentcapable class can be deployed into a JDO Implementation, it must be “enhanced.” An
enhancer is a tool provided by the JDO vendor that manipulates the byte code of the
compiled application classes. The resulting enhanced byte code represents a version of
the application class that does implement the PersistenceCapable interface.
This whole process possibly sounds somewhat peculiar, but compare it to the approach
used in EJB for CMP Entity beans. As you recall, the bean developer creates an abstract
class with abstract getter and setter methods for each of the cmp-fields. The EJB container vendor’s deployment tools then generate an implementation for those methods and
create the database access code. The enhancement process effectively does the equivalent
of the first of these two tasks and also imbues the application class with a reference to a
StateManager instance. The StateManager itself takes on the job of database access and
persistence.
11 0672323842 CH08
3/20/02
9:30 AM
Page 389
Transactions and Persistence
The final interface, shown in Figure 8.13, is the InstanceCallbacks interface. The application developer can choose to implement this or not. If implemented, the methods give
the application class visibility as to the transaction boundaries. You may have spotted that
this is pretty similar to EJB’s optional SessionSynchronization interface.
Time for some code! Listing 8.10 shows the basic steps to create a new persistencecapable Customer.
LISTING 8.10
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Using JDO to Create a New Customer
// import javax.jdo.*;
// import javax.naming.*;
Context ctx = new InitialContext();
PersistenceManagerFactory pmf = (PersistenceManagerFactory)
ctx.lookup(“java:comp/env/jdo/SomePersistenceManagerFactory”;
PersistenceManager pm = pmf.getPersistenceManager();
Transaction txn = pm.currentTransaction();
txn.begin();
Customer customer = new Customer(login, address1, address2, email, name);
pm.makePersistent(customer);
txn.commit();
That’s honestly all there is to it! Customer is just a regular Java class that has been run
through the JDO Enhancer.
Queries
As well as being able to create new objects, JDO also provides the facility to find existing objects. This is not surprising; after all, the home interface offers the finder methods
as well as the create methods in EJB.
Rather than defining a declarative language, such as SQL or EJBQL, JDO uses a Java
API approach. Figure 8.14 shows the classes and interfaces of javax.jdo that allow
queries to be performed.
The PersistenceManager.newQuery() method will instantiate a javax.jdo.Query
object. The set of candidate objects to be returned by the query is then configured using
the setCandidates() method. This is overridden to accept either a
java.util.Collection or a javax.jdo.Extent (more on Extent shortly). A filter can
also be defined using setFilter(), as can parameters to the filter. Variables are used to
declare iterators over multi-valued collection fields.
389
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 390
390
Day 8
FIGURE 8.14
interface
javax.jdo.Query
interface
…jdo.PersistenceManager
Classes and interfaces
that support JDO
queries.
newQuery
getExtent
getObjectById
com.vendor.PMImpl
1
Conceptually, all
Persistence Capable
objects are
partitioned into
extents by class.
1
«creates»
<{java.lang.Class}>
interface
javax.jdo.Extent
0..*
iterator
hasSubclasses
getCandidateClass
getPersistenceManager
closeAll
close
setClass
setCandidates
setFilter
declareImports
declareParameters
declareVariables
setOrdering
setIgnoreCache
getIgnoreCache
compile
execute
executeWithMap
executeWithArray
getPersistenceManager
close
closeAll
com.vendor.QueryImpl
0..1
candidates
com.vendor.ExtentImpl
Not all methods
shown.
0..*
0..*
active
inExistence
interface
…jdo.PersistenceCapable
0..*
finds
The Extent interface is principally used to identify all instances of some specified class
in the persistent data store. Conceptually, this could be a very large set of objects, so
commercial JDO implementations are not expected to fully materialize the set. Indeed,
the JDO specification explicitly requires that JDO implementations must not cause an
out-of-memory error when instantiating an Extent. The java.util.Iterator returned
by the iterator() method must be able to iterate over all instances if needed, but notice
that other methods of the java.util.Collection (such as contains() and isEmpty())
are not present in the Extent interface. In practice, the Extent and Query implementations will be closely coupled to provide efficient ways to identify the qualifying
persistence-capable objects.
As an example, Listing 8.11 shows two queries. The first identifies all jobs that have a
customer of “winston”; the second finds all jobs that require “Cigar Trimmer” as a
skill.
LISTING 8.11
Using JDO to Search for Jobs that Meet Some Criteria
1: // import javax.jdo.*;
2: // import javax.naming.*;
3:
4: Context ctx = new InitialContext();
11 0672323842 CH08
3/20/02
9:30 AM
Page 391
Transactions and Persistence
LISTING 8.11
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:
391
Continued
PersistenceManagerFactory pmf = (PersistenceManagerFactory)
ctx.lookup(“java:comp/env/jdo/SomePersistenceManagerFactory”;
PersistenceManager pm = pmf.getPersistenceManager();
Transaction txn = pm.currentTransaction();
txn.begin();
// query #1: all jobs that have a customer of “winston”.
Query query1 = pm.newQuery();
query1.setClass(Job.class);
Extent candidateJobs = pm.getExtent(Job.class, false);
query1.setCandidates(candidateJobs);
query1.declareParameters(“String nameParam”);
query1.setFilter( “customer == nameParam “);
Collection query1Res = (Collection) query.execute(“Winston”);
for(Iterator iter = query1Res.iterator(); iter.hasNext(); ) {
Job job = (Job)iter.next();
System.out.println(job.getRef());
}
// query #2: all jobs that require “Cigar Trimmer” as a skill
Query query2 = pm.newQuery(Job.class, candidateJobs);
query2.declareVariables(“Skill eachSkill”);
query2.setFilter(
➥”skills.contains(eachSkill) &&
➥eachSkill.name == \”Cigar Trimmer\””);
Collection query2Res = (Collection) query.execute();
for(Iterator iter = query2Res.iterator(); iter.hasNext(); ) {
Job job = (Job)iter.next();
System.out.println(job.getRef());
}
txn.commit();
Other Features
There is much more in JDO than there is room to cover here. Some aspects worth a brief
mention include the following:
• Deployment descriptors—A deployment descriptor is used for each persistencecapable application class. This identifies the fields that are persistent rather than
transient, the JDO identity type, and other information required for the JDO
enhancer.
• Second Class Objects (SCOs)—These are similar in concept to dependent value
classes in EJBs, in that they are persistent only by virtue of being reachable from
First Class Objects (all objects previous discussed have been First Class Objects).
In particular, they do not have a JDO identity. If an SCO is modified, it must
explicitly notify its containing First Class Object.
8
11 0672323842 CH08
3/20/02
9:30 AM
392
Page 392
Day 8
The JDO specification identifies various classes in the JDK library packages that
are first- or second-class, or that are not persistable at all (for example,
java.net.Socket).
• Lifecycle—Persistent-capable objects go through different stages of their lifecycle.
For example, when an object is first instantiated, it is in the transient state. When
the PersistenceManager.makePersistent() method is called, the object transitions to persistent-new state. One state, hollow, is very similar to the EJB notion of
a passivated Entity bean.
• Transient transactional objects—These are persistent-capable objects acting in a
manner akin to a ShoppingCart Session bean whose implementation supports automatic transaction recovery (implementing javax.ejb.SessionSynchronization).
Gotchas
The following are some “gotchas” to help you with your implementation:
• If writing CMTD Session beans, resource manager transaction methods (such as
connection.commit()) must not be used.
• The ejbCreate() and ejbRemove() lifecycle methods for Session beans (and
Message-driven beans) are performed with unspecified transaction context.
• The collection returned by the getter method for a cmr-field cannot be used outside
of the transaction in which it was materialized (see EJB specification, section
10.3.8).
• If using a SQLj Part 0 compiler, make sure that the vendor offers adequate support
for the generated code to run within the J2EE environment.
• If using SQLj Part 1 or Part 2, be sure to do some performance benchmarking first.
This is a new technology and JVM-enabled features within the RDBMS are highly
unlikely to perform as well as raw SQL.
• If the javax.transaction.UserTransaction interface is used for transaction
demarcation within BMTD Session beans that are using JDO for persistence, the
javax.jdo.PersistenceManager must be acquired after the transaction has been
started.
Moreover, the PersistenceManager cannot be used after the transaction has complete. It is better to use the javax.jdo.Transaction that does not require a new
PersistenceManager for each new transaction.
• One for you to revisit after Day 9, “Java Messaging Service,” and Day 10,
“Message-Driven Beans”—the JMS request/reply paradigm cannot be used for
transacted sessions (see EJB specification, section 17.3.5).
11 0672323842 CH08
3/20/02
9:30 AM
Page 393
Transactions and Persistence
Summary
Curious day, today. If you’ve skipped over all the sections that discuss technologies that
aren’t appropriate to you, you might still be feeling pretty fresh. On the other hand, if
you’ve been trying to wrap your head around all these new and peculiar concepts, you
could be completely worn out.
Anyway, today you’ve learned that EJB containers support container-managed transaction demarcation, but that your Session beans can take control using bean-managed
transaction demarcation if needed. Behind the scenes, there are some complex goings on
with the XA interfaces to support distributed transactions, using 2PC and XA-aware data
store connections, such as javax.sql.XADataSource.
JDBC is the de-facto way for implementing persistence of Java objects, and JDBC 3.0
unifies the various classes and interfaces in the java.sql and javax.sql packages.
JDBC 2.1 and 3.0 have fleshed out support for the most relevant features of SQL1999,
specifically advanced data types.
The SQLj initiative addresses both client-side integration of SQL and Java (part 0) and
server-side (that is, RDBMS) integration (parts 1 and 2). SQLj part 0 is more succinct
than JDBC, but is not particularly well tailored to the J2EE environment, because for the
most part, it pre-dates it. SQLj parts 1 and 2 offer some opportunities to radically change
the way that databases are used, with SQLj part 2 operating in much the same space as
the SQL1999 advanced data types.
JDO is a new specification that aims to make Java persistence totally transparent. It is a
natural fit for OODBMS and O/R mapping tool vendors and also works as a front-end to
ERP systems. JDO primarily focuses on the API into the client-side cache of persistent
objects, leaving much of the back-end configuration to the JDO implementation vendors.
Q&A
Q Why would it be pointless to deploy the getter of a multi-valued cmr-field
using the RequiresNew transaction attribute?
A This would be pointless because the returned collection cannot be used outside of
the transaction in which it was materialized.
Q What happens to the current transaction when an EJB throws an application
exception?
A The bean may or may not mark the transaction for rollback; consult the bean’s documentation.
393
8
11 0672323842 CH08
3/20/02
9:30 AM
Page 394
394
Day 8
Q When using SQLj Part 2, why should the Java class acting as an abstract data
type define a static variable serialVersionUID?
A To provide forward compatibility with future versions of the class.
Exercises
A nice short exercise for you today.
The starting point for the exercise is a version of the today’s case study that uses the
BMTD for the AdvertiseJob bean. Convert the Register bean (that deals with applicants) to use BMTD rather than CMTD. The code to work on is under day08\exercise.
The steps you should perform are as follows:
1. Make sure that you are still using the CMP version of the Agency database.
Although you won’t be changing them, the Entity EJBs for the exercise are the
CMP versions from yesterday.
2. Update the implementation of the various methods of RegisterBean class, basing
it on AdvertiseJobBean. You will probably find it helpful to copy over the 3 helper
methods (beginTransactionIfRequired(), statusAsString(), and
completeTransactionIfRequired()).
3. Modify the dd\agency_session_ejbs-ejb-jar.xml deployment descriptor, changing RegisterBean to use transaction-type of Bean. You can also remove the
RegisterBean’s container-transaction elements under the assemblydescriptor element because they will no longer be needed.
4. Build the enterprise application (agency.ear in the jar directory) and then load it
into deploytool. Deploy and complete any information missing from the wizard.
5. Test your program using the AllClients client, run with run\runAll.
The solution is in day08\agency.
12 0672323842 CH09
3/20/02
9:23 AM
Page 395
WEEK 2
DAY
9
Java Message Service
So far, you have learned about two types of EJB—Entity Beans and Session
Beans. Before introducing you to the third type—Message Beans (to be covered on Day 10, “Message-Driven Beans”), we will take a look at the purpose
and use of messaging within enterprise applications, the Java Message Service’s
API (JMS), and its place within J2EE.
Messaging
Many enterprise applications are built from separate software components.
These components can reside on the same system or in a distributed or multitiered environment on several Java Virtual Machines.
Some method of communication is almost always required between the components in large systems. It is often a requirement that this communication should
also be loosely coupled or asynchronous. In asynchronous communication, the
sender sends the message and continues execution without waiting for a reply,
and the receiver can retrieve the message at any time after it has been sent. In a
loosely coupled system, the sender does not need to necessarily know who the
recipient is; the communication itself may or may not be asynchronous. This
12 0672323842 CH09
3/20/02
9:23 AM
Page 396
396
Day 9
communication between software components is called messaging.
Without support for a messaging system, the programmer would typically use a sockets
interface for inter-application communication. With sockets, both the sender and receiver
need to agree on the socket address, and both applications need to be running at the same
time. Likewise, if Remote Method Invocation (RMI) is used, the sender (or calling application) needs to know about the receiver’s (or remote application’s) methods. With messaging, the sender and receiver only have to agree on the message format and where to
send it. The sender and receiver do not need to know anything about each other, nor do
they both need to exist at the same time.
Messaging should be used in preference to a tightly coupled API such as RMI when
some or all of the following conditions exist:
• The components interfaces are not known or not published.
• Not all of the components will be running at the same time.
• A sender needs to communicate with multiple receivers.
• No response is required.
Messaging should not be confused with electronic mail (e-mail). E-mail is used to communicate between people, whereas messaging is the mechanism used to communicate
between applications.
Messaging is used in various environments and many applications; it is not unique to
J2EE. A well-known example of a messaging service is IBM’s MQ Series. While there
are numerous messaging APIs, the only one supported by J2EE is JMS.
Message Passing
There are several models of message passing, but JMS only supports two—point-to-point
and publish/subscribe. Both of these are known as a push models. The sender of the message is the active initiator, and the receiver is a passive consumer.
With point-to-point, the sender and receiver agree on a message destination, also known
as a queue. The sender leaves the message and the receiver picks up the message at any
time thereafter. The message remains in the queue until the receiver removes it. The following are some real-life examples of point-to-point communication:
• Sending a fax
• Dropping letters at a hotel desk to be picked up later by a hotel guest
• Leaving a voice mail message
Figure 9.1 illustrates point-to-point messaging.
12 0672323842 CH09
3/20/02
9:23 AM
Page 397
Java Message Service
FIGURE 9.1
397
Point-to-Point
Point-to-point
messaging model.
Message
Receiver
Message
Consumes message
and sends acknowledgement
Message
Sender
Message
sends message
to queue
9
Queue
With the publish/subscribe model, the sender, now called a publisher, again sends messages to an agreed destination. The destination is, by convention, known as a topic and
this time there may be many receivers and these are called subscribers. Messages are
immediately delivered to all current subscribers and are deleted when all the subscribers
have received the message. Figure 9.2 portrays the publish/subscribe messaging model.
FIGURE 9.2
Publish/Subscribe
Publish/Subscribe
messaging model.
Subscriber
Consumes message
and sends acknowledgement
Subscriber
Publisher
Topic
publishes message
to topic
Consumes message
and sends acknowledgement
Subscriber
Consumes message
and sends acknowledgement
Within JMS, these models are called message domains. Code examples and further
details on the JMS implementation of these message domains are presented in the next
section.
Java Message Service API
The JMS API is a Message-Oriented Middleware (MOM) API. It was designed by a collaboration of several companies, including Sun Microsystems and IBM. Version 1 of the
API was released in August, 1998. The purpose of JMS is to enable applications to transmit and receive messages in an asynchronous and reliable way. The JMS API also
12 0672323842 CH09
3/20/02
9:23 AM
Page 398
398
Day 9
defines interfaces to allow applications written using JMS to communicate with other
messaging APIs.
The JMS designers’ aims were as follows:
• To minimize the messaging concepts that a programmer needs to understand
• Provide communication that is loosely coupled, asynchronous, and reliable
• Have a consistent API that is independent of the JMS provider
• Maximize the portability of JMS applications
The JMS API provides messaging that is
• Loosely coupled—The sender and receiver may have no information about each
other or the mechanisms used to process messages.
• Asynchronous—Receivers do not request messages. Messages can be delivered as
they arrive. The sender does not wait for reply.
• Reliable—A message is sent and received once and only once.
Note the JMS API does not specify how to control the privacy and integrity of messages.
The type and level of message security is left to the JMS providers and is configured by
administers not by J2EE clients.
JMS and J2EE
Prior to J2EE 1.3, it was not necessary to implement the JMS API. From version 1.3, the
JMS API became an integral part of the J2EE requirements.
Support for JMS within the J2EE platform provides the following functionality and features:
• Message beans for the sending and receiving of asynchronous messages (message
beans are covered on Day 10)
• Support for distributed transactions
• Concurrent consumption of messages
An additional goal is to provide loosely coupled, reliable, asynchronous communication
with legacy systems.
12 0672323842 CH09
3/20/02
9:23 AM
Page 399
Java Message Service
399
Programmatically, JMS can be considered to be a container-managed resource, similar to
a JDBC connection. Application clients, EJBs, and Web components can all utilize the
JMS API to send and receive messages. Note, however, that Applets are not required to
support JMS.
The addition of the JMS API to J2EE simplifies enterprise software development and
provides access to existing Enterprise Information Systems (EIS) and other legacy systems.
JMS API Architecture
Before launching into the JMS API programming model, there are some details of the
JMS API architecture that need to be covered. Table 9.1 contains a list of the J2EE components that are found in a JMS application.
TABLE 9.1
JMS Components
Component
Description
JMS Provider
This is the messaging system that implements the JMS API interfaces and
provides certain control features. All J2EE implementations from J2EE
1.3 and later must include a JMS provider.
JMS Clients
System components or programs that send or receive messages.
Messaging Domains
JMS supports both the point-to-point and publish/subscribe message
domains. Both must be implemented in J2EE 1.3 and later.
Messages
These are the objects used to communicate between JMS clients.
Queues
Used to hold messages in the point-to-point domain.
Topics
Used to hold messages in the publish/subscribe domain.
Administered Objects
These are pre-configured JMS objects used to create connections with a
JMS Provider (a connection factory) or to specify the target or source of
messages (queues or topics).
Connection
A client’s active connection to the JMS provider.
Session
One or more sessions can be created for each connection. Used to create
senders and receivers and administer transactions.
Native Clients
Clients that use a non-JMS messaging API.
Figure 9.3 is a UML activity diagram of how the components fit together in a JMS application.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 400
400
Day 9
FIGURE 9.3
JMS application components.
Message
Generator
JMS
JNDI
lookup
Connection
Factory
create
Connection
create Session
lookup Queue/
Topic
create Sender/
Publisher
create Message
send Message
No more
messages
More
messages
Message Domains
JMS supports two types of message domains—point-to-point and publish/subscribe, giving application developers both choice and flexibility in message handling.
JMS Point-to-Point Messaging Domain
With point-to-point messaging the application is built around message queues, with a
one-to-one relationship between a sender and a receiver. Each sender addresses the message to, and the receiver removes messages from, a queue. Many receivers can access the
same queue, but only the first to pick up the message will receive it. The sender may set
an expiration time on a message, after which it will be deleted from the queue.
There is no mechanism to send a message to a particular receiver. If this one-to-one relationship is required, the message must be sent to a queue that has only one receiver.
12 0672323842 CH09
3/20/02
9:23 AM
Page 401
Java Message Service
401
A JMS point-to-point message domain has the following characteristics:
• Each message is produced by the sender and consumed by one and only one
receiver.
• Messages are either consumed by the receiver or they time out and are deleted by
the JMS provider.
• Receivers can consume the message any time after it has been sent.
• The receiver does not need to exist when the message is produced.
• The receiver cannot request a message.
• The receiver must acknowledge receipt of the message.
The JMS API does not allow a single message to be sent to multiple queues. If you need
to send a message to multiple receivers, you should consider using the publish/subscribe
message domain.
JMS Publish/Subscribe Messaging Domain
The JMS publish/subscribe messaging domain has a completely different delivery model
from point-to-point. With the publish/subscribe model, senders post messages to a topic;
many receivers can register interest in a topic by subscribing to it.
Messages in a topic are distributed to all subscribers and only exist in the topic for as
long as this process takes. A receiver can only receive messages posted after it has subscribed to a topic, and the receiver must be active at the time the message is posted to
receive it. This introduces a timing dependency between the sender and the receivers that
is not present in point-to-point messaging.
To circumvent this timing dependency, the JMS API allows receivers to create durable
subscriptions. Durable subscriptions will be covered in more detail in the section titled
“Creating Durable Subscriptions,” later on in this chapter.
A JMS publish/subscribe message domain has the following characteristics:
• Each message is produced by a publisher and consumed by zero or more subscribers.
• Messages are immediately distributed to the existing subscribers.
• Subscribers must exist at the time the message is published to receive the message.
• Durable subscriptions can be used to allow subscribers to receive messages published to a topic when the subscriber was inactive.
• The subscriber must acknowledge receipt of the message.
Table 9.2 lists the JMS objects in the point-to-point domain.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 402
402
Day 9
TABLE 9.2
Components in the JMS Point-to-Point Message Domain
Component
Description
QueueConnectionFactory
Administered object used to create an object that implements the
QueueConnection interface
QueueConnection
Active connection to a JMS provider
QueueSession
Provides methods for creating objects that implement the
QueueSender QueueReceiver or QueueBrowser interfaces
QueueSender
Used by a client to send a message to a queue
QueueReceiver
Used to receive messages sent to a queue
Queue
Encapsulates the JMS provider queue name
QueueBrowser
Used to look at messages on a queue without removing them
Developing JMS Applications Using JBoss1
Shortly, you will be developing a simple JMS application. As with all the code examples
in this book, the application will be developed using J2EE RI. Although J2EE RI provides a good environment for learning about JMS, it is not intended to support commercial applications. Before developing this JMS application, there will be a short digression
to show the JMS features available with JBoss.
JBoss is in an open-source implementation of J2EE developed by the JBoss Group.
JBoss is available to download for free from www.jboss.org.
JMS Implementation in JBoss
JBoss contains a JMS provider called JBossMQ. JBossMQ is fully JMS API-compliant
and can be used for standalone JMS clients. JBossMQ has a number of configuration
files, the most important of which is called jboss.jcml. This file resides under the JBoss
installation in the conf/default directory. To create a message application in Jboss, you
first have to read or edit this file to
1. Look up the names of the available connection factories
2. Add destinations (queues and topics)
Additionally, you can optionally add client identifiers (JBossMQ users) to the jbossmqstate.xml file (also in conf/default) for authentication. The purpose and use of
authentication is to associate a client connection and its objects with a state maintained
by the JMS provider. At the current time, the only state defined is that required to support durable subscriptions.
12 0672323842 CH09
3/20/02
9:23 AM
Page 403
Java Message Service
403
Administered Objects
Connection factories and destinations are the administered objects that are configured by
the JMS administrator.
Connection Factory
The purpose of a connection factory is to encapsulate connection configuration. Clients
use a pre-configured connection factory to create connections to the JMS provider.
JBoss includes several different connection factories with varying characteristics, as
shown in Table 9.3.
TABLE 9.3
Connection Factories Provided in JBoss
JNDI Name
Description
ConnectionFactory
The default factory. A fast two-way socket-based communication protocol.
XAConnectionFactory
Also a fast two-way socket based protocol that also has support for XA transactions.
RMIConnectionFactory
Uses RMI to implement communication mechanism.
RMIXAConnectionFactory
Uses RMI and also has support for XA transactions.
java:/ConnectionFactory
Very fast in-VM protocol that does not use sockets. Available
when the client is in the same virtual machine as JBossMQ.
java:/XAConnectionFactory
Fast in-VM protocol that also. supports XA transactions.
UILConnectionFactory
Protocol multiplexed over one socket. Used when going
through firewalls or when the client is not able to look up the
server IP address correctly.
UILXAConnectionFactory
Same as UILConnectionFactory but also has support for XA
transactions.
You should choose the connection factory appropriate for your application. The default
connection factory in JBossMQ is ConnectionFactory, which is implemented over a
two-way, socket-based communication protocol.
Note that other JMS providers may have different connection factories with different
characteristics and different names.
Destinations
Destinations define the address parameters for queues and topics. New queue and topic
destinations are added to the jboss.jcml file with the following:
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 404
404
Day 9
<mbean code=”org.jboss.mq.server.TopicManager”
➥ name=”JBossMQ:service=Topic,name=YourTopicName”/>
<mbean code=”org.jboss.mq.server.QueueManager”
➥ name=”JBossMQ:service=Queue,name=YourQueueName”/>
You have now finished looking at JBoss. The following examples illustrate how to connect to queues and topics using J2EE RI.
Programming a JMS Application Using
J2EE RI
In this section, you will develop a simple point-to-point application that sends and
receives a text message. Although this code has not been written for any one particular
JMS implementation, you will need to be aware that the mechanism for setting up the
administered objects is implementation specific. Also, the JNDI names may be different
for different J2EE environments.
J2EE RI Connection Factories
With the J2EE RI, you will use the default connection factory objects, named
jms/QueueConnectionFactory and jms/TopicConnectionFactory.
Adding Destinations in J2EE RI
J2EE RI provides a command-line program called j2eeadmin to list, create, modify, and
delete queues and topics. There is also a screen in the J2EE RI Application Deploy Tool
that provides an interface to j2eeadmin for the same purpose.
Creating a Queue in J2EE RI
The point-to-point application uses a queue for communication. To create a queue do the
following:
1. Ensure that J2EE server is running.
2. Use j2eeadmin or deploytool to create the queue.
Create a Queue Using j2eeadmin
To see the existing queues and topics, use the following:
j2eeadmin –listJMSDestination
The J2EE RI has a default queue predefined—jms/Queue.
12 0672323842 CH09
3/20/02
9:23 AM
Page 405
Java Message Service
405
To add a queue called jms/firstQueue, use the following:
j2eeadmin –addJMSDestination jms/firstQueue queue
The j2eeadmin command works silently, so to check that your queue has been created,
run J2eeadmin -listJMSDestination once more. Figure 9.4 demonstrates the use of
these two commands to create a queue called jms/firstQueue (this is the queue you will
use in your first example).
9
FIGURE 9.4
Using j2eeadmin to
add a JMS queue to
J2EE RI.
Create a Queue Using deploytool
You can also use deploytool to list and add queues. Start up deploytool and select Server
Configuration from the Tools menu, as shown in Figure 9.5.
FIGURE 9.5
Using deploytool to
configure installation.
12 0672323842 CH09
3/20/02
9:23 AM
Page 406
406
Day 9
This will bring up the Configure Installation screen. Select JMS Destinations in the
panel on left to display the screen in Figure 9.6.
FIGURE 9.6
Adding JMS destination in deploytool.
Click the Add button in the panel at bottom right titled JMS Queue Destinations and add
a new queue called jms/firstQueue.
Point-to-Point Messaging Example
You are now in a position to start coding. For all the following code examples, you will
need to import the javax.jms package. This contains all the classes for creating connections, sessions, queues, and topics.
To send a JMS message, a number of steps must first be performed to obtain a connection factory, establish a session, and create a QueueSender object. Although the example
is in the point-to-point message domain, the same steps are required for publish/subscribe topics.
The following steps show how to create a queue used later to send a simple text message.
1. Obtain the JNDI initial context.
Context context = new InitialContext();
2. Contact the JMS provider, obtain a JMS connection from the appropriate
ConnectionFactory, and create a connection for a queue. The following code uses
a connection factory registered against the default JNDI name
jms/QueueConnectionFactory.
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(“jms/QueueConnectionFactory”);
QueueConnection queueConnection = queueFactory.createQueueConnection();
12 0672323842 CH09
3/20/02
9:23 AM
Page 407
Java Message Service
407
The createQueueConnection() method throws a JMSException if the JMS
provider fails to create the queue connection due to some internal error.
3. Establish a QueueSession for this connection. In this case, the QueueSession has
transactions set to false (transactions are covered later) and AUTO ACKNOWLEDGE of
receipt of messages. This will throw a JMSException if the QueueConnection
object fails to create a session.
QueueSession queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
Note
Although sent messages are not acknowledged, a session can be used to
receive messages created by its own connection. This is why an
Acknowledge mode must be specified on a session, even if the queue is only
used to send messages.
4. Obtain the queue destination using its JNDI name as defined using j2eeadmin or
deploytool. The lookup() method can throw a NamingException if the name is
not found.
queue = (Queue)context.lookup("jms/firstQueue");
5. Finally, create a queue sender that will be used to send messages. This will throw a
JMSException if the session fails to create a sender, and an
InvalidDestinationException if an invalid queue is specified.
QueueSender queueSender = queueSession.createSender(queue);
Note how the connection factory hides all the implementation details of the connection
from the client. It does the hard work of creating resources, handling authentication, and
supporting concurrent use.
Now you have a queue that is ready to send messages. But before that, you need to know
a little more about JMS messages.
JMS Messages
JMS messages consist of three parts:
• A header—Used to identify messages, set priority and expiration, and so on and to
route messages.
• Properties—Used to add additional information in addition to the message header.
• Message body—There are five message body forms defined in JMS—
BytesMessage, MapMessage, ObjectMessage, StreamMessage, and TextMessage.
Note that only the header is a required component of a message; the other two parts,
including the body, are optional.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 408
408
Day 9
Message Header Fields
The JMS message header contains a number of fields that are generated by the JMS
provider when the message is sent. These include the following:
•
JMSMessageID
A unique identifier
•
JMSDestination
Represents the queue or topic to which the message is sent
•
JMSRedelivered
Set when the message has been resent for some reason
The following three header fields are available for the client to set:
•
JMSType
A string that can be used to identify the contents of a message
•
JMSCorrelationID
Used to link one message with another, typically used to link
responses to requests
•
JMSReplyTo
Used to define where responses should be sent
Other header fields may be set by the client but can be overridden by the JMS provider
with figures set by an administrator:
•
JMSDeliveryMode
This can be either PERSISTENT or NON_PERSISTENT (the default
is PERSISTENT).
•
JMSPriority
Providers recognize priorities between 0 and 9, with 9 being the
highest (default is 4). Note that there is no guarantee that higher priority messages
will be delivered before lower priority ones.
•
JMSTimestamp
•
JMSExpiration
This contains the time the message was sent to the JMS provider
by the application. Note that this is not the time the message is actually transmitted
by the JMS provider to the receivers.
An expiration time
Each header field has associated setter and getter methods that are fully described on the
JMS API documentation.
Message Properties
As you have seen, there is not a great deal of scope for clients to add information to JMS
header fields, but additionally JMS messages can incorporate properties. These are
name/value pairs defined by the client.
Property values can be boolean, byte, short, int, long, float, double, or String and
are defined using the appropriate Message.setProperty method. For example:
message.setStringProperty (“Type”, “Java”);
sets the message object’s property called “Type” to the string “Java”. A corresponding
getProperty method is used to retrieve a message’s property by name. The
getPropertyNames() can be used if the names are not known.
12 0672323842 CH09
3/20/02
9:23 AM
Page 409
Java Message Service
409
The prefix JMSX is used for JMS-defined properties. Inclusion of these JMSX properties
are optional; refer to your JMS provider documentation to determine what properties are
supported in your JMS implementation.
JMS Body Types
JMS supports five types of message body. Each is defined by its own message interface.
Each type is only briefly described in Table 9.4. Refer to the JMS API for more information on the message body types.
TABLE 9.4
JMS Message Body Types
Message Body Type
Message Contents
BytesMessage
Un-interpreted byte stream.
MapMessage
Name/value pairs
ObjectMessage
Serializable Java object
StreamMessage
Stream of Java primitives
TextMessage
Java String
Creating a Message
For the example given here, the default header properties will be used. Also, to keep this
example straightforward, a TextMessage body will be used (this is also the most common message body type). A TextMessage object extends the Message interface and is
used to send a message containing a String object.
A text message body is created using the createTextMessage() method in the
object.
QueueSession
TextMessage message = queueSession.createTextMessage(String);
The content of the a text message is added with message.setText():
String msg = “some text”;
message.setText(msg);
Having created a message, you are ready to send it.
Sending a Message
A message must be sent to a queue QueueSender object as follows:
queueSender.send(queue, message);
It’s as simple as that.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 410
410
Day 9
Closing the Connection
Connections are relatively heavyweight JMS objects, and you should always release the
resources explicitly rather than depending on the garbage collector. The sender, the session, and the connection should all be closed when no more messages need to be sent.
queueSender.close();
queueSession.close();
queueConnection.close();
With the J2EE RI implementation, prior to releasing the QueueSender object, you must
send a empty message to indicate no more messages will be sent. To do this, add the following line before the QueueSender object is closed.
queueSender.send(queueSession.createMessage());
Note
This code may not be required in other JMS implementations.
Send JMS Text Message Example
The code for the entire point-to-point sender example is shown in Listing 9.1.
LISTING 9.1
Complete Listing for Point-to-Point Queue Sender
1: import javax.naming.*;
2: import javax.jms.*;
3:
4: public class PTPSender {
5:
private QueueConnection queueConnection;
6:
private QueueSession queueSession;
7:
private QueueSender queueSender;
8:
private Queue queue;
9:
10:
public static void main(String[] args) {
11:
try {
12:
PTPSender sender = new PTPSender();
13:
14:
System.out.println (“Sending message Hello World”);
15:
sender.sendMessage(“Hello World”);
16:
sender.close();
17:
} catch(Exception ex) {
18:
System.err.println(“Exception in PTPSender: “ + ex);
19:
}
12 0672323842 CH09
3/20/02
9:23 AM
Page 411
Java Message Service
LISTING 9.1
411
Continued
20: }
21:
22:
public PTPSender() throws JMSException, NamingException {
23:
Context context = new InitialContext();
24:
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(“jms/QueueConnectionFactory”);
25:
queueConnection = queueFactory.createQueueConnection();
26:
queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
27:
queue = (Queue)context.lookup(“jms/firstQueue”);
28:
queueSender = queueSession.createSender(queue);
29:
}
30:
31:
public void sendMessage(String msg) throws JMSException {
32:
TextMessage message = queueSession.createTextMessage();
33:
message.setText(msg);
34:
queueSender.send(message);
35:
}
36:
37:
public void close() throws JMSException {
38:
//Send a non-text control message indicating end of messages
39:
queueSender.send(queueSession.createMessage());
40:
queueSender.close();
41:
queueSession.close();
42:
queueConnection.close();
43:
}
44:}
To send messages to the queue, run this program from the command line. The next
example program will retrieve messages from that same queue for display onscreen.
Consuming Messages
There are two ways of consuming queued messages with JMS.
• Synchronously—An explicit call to the receive() method.
• Asynchronously—By registering an object that implements the MessageListener
interface. The JMS provider invokes the onMessage() method in this object each
time there is a message queued at the destination.
Figure 9.7 is a UML sequence diagram showing the difference between synchronous and
asynchronous message consumption.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 412
412
Day 9
FIGURE 9.7
client
Synchronous and asynchronous message consumption.
:Receiver
receive()
method call
blocks until
message
arrives
Synchronous
Message
Asynchronous
setMessageListener(this)
next message
arrives
onMessage(Message)
The following example will demonstrate how to receive a message from a queue using a
synchronous call.
Simple Synchronous Receiver Example
The code for a simple synchronous receiver is very similar to that of the sender method
already presented. There are two differences in constructing the QueueReceiver object.
The first one is obvious; a QueueReceiver is created instead of a QueueSender. Like
the createReceiver() method throws a JMSException if the session
fails to create a receiver, and an InvalidDestinationException if an invalid queue is
specified.
createSender(),
The second difference is that this time there is a call to the connection’s start() method,
which starts (or restarts) delivery of incoming messages for this receiver. Calling start()
twice has no detrimental effect. It also has no effect on the connection’s ability to send
messages. The start() method may throw a JMSException if an internal error occurs. A
receiver can use Connection.stop() to temporarily suspend delivery of messages.
The message is received using the synchronous receive() method, as shown next. This
may throw a JMSException.
Message msgBody =queueReceiver.receive();
if (msgBody instanceof TextMessage) {
String text = ((TextMessage) msgBody).getText();
}
12 0672323842 CH09
3/20/02
9:23 AM
Page 413
Java Message Service
413
If there is no message in the queue, the receive() method blocks until a message is
available. There are two alternative versions of the receive() method:
•
receiveNoWait()
Retrieves the next message available, or returns null if one is
not immediately available
•
receive(long timeout)
Retrieves the next message produced within the period
of the timeout
Receive JMS Text Message Example
The code for the entire point-to-point receiver example is shown in Listing 9.2.
LISTING 9.2
Complete code for Point-to-Point Queue Receiver
1: import javax.naming.*;
2: import javax.jms.*;
3:
4: public class PTPReceiver {
5:
6:
private QueueConnection queueConnection;
7:
private QueueSession queueSession;
8:
private QueueReceiver queueReceiver;
9:
private Queue queue;
10:
11:
public static void main(String[] args) {
12:
try {
13:
PTPReceiver receiver = new PTPReceiver();
14:
System.out.println (“Receiver running”);
15:
String textMsg = receiver.consumeMessage();
16:
if (textMsg != null)
17:
System.out.println (“Received: “ + textMsg);
18:
receiver.close();
19:
}
20:
catch(Exception ex) {
21:
System.err.println(“Exception in PTPReceiver: “ + ex);
22:
}
23:
}
24:
25:
public PTPReceiver() throws JMSException, NamingException {
26:
Context context = new InitialContext();
27:
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(“jms/QueueConnectionFactory”);
28:
queueConnection = queueFactory.createQueueConnection();
29:
queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
30:
queue = (Queue)context.lookup(“jms/firstQueue”);
31:
queueReceiver = queueSession.createReceiver(queue);
32:
queueConnection.start();
33:
}
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 414
414
Day 9
LISTING 9.2
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49: }
Continued
public String consumeMessage () throws JMSException {
String text = null;
Message msgBody = queueReceiver.receive();
if (msgBody instanceof TextMessage) {
text = ((TextMessage) msgBody).getText();
}
return text;
}
public void close() throws JMSException {
queueReceiver.close();
queueSession.close();
queueConnection.close();
}
Run this program from the command line. If you have previously used PTPSender to put
messages in the queue, these will now be displayed.
Asynchronous Messaging
For many applications, the synchronous mechanism is not suitable and an asynchronous
technique is required. To implement this in JMS, you need to register an object that
implements the MessageListener interface. The JMS provider invokes this object’s
onMessage() each time a message is available at the destination.
The receiver example will now be extended to support asynchronous messaging. Because
this is a simple example, the listener is implemented in the same class.
public class PTPListener implements MessageListener {
The message listener is registered with a specific QueueReceiver by using the
setMessageListener() method before calling the connection’s start() method. The
following extra line is required in the constructor:
queueReceiver.setMessageListener(this);
Messages might be missed if you call start() before you register the message listener.
To actually receive the messages, the MessageListener interface provides a single
onMessage() method. The JMS provider calls your implementation of this method when
it has a message to deliver. The following is an example onMessage() method:
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
12 0672323842 CH09
3/20/02
9:23 AM
Page 415
Java Message Service
415
String text = ((TextMessage) message).getText();
System.out.println(“Received: “ + text);
}
}
catch(JMSException ex) {
System.err.println(“Exception in OnMessage: “ + ex);
}
}
The onMessage() method should handle all exceptions. If onMessage() throws an exception, the signature will be altered and, therefore, not recognized.
In the main() method, the QueueReceiver object is initialized as before, but this time
there is no call to a synchronous receive() method.
public static void main(String[] args) {
System.out.println (“Listener running”);
try {
PTPListener receiver = new PTPListener();
Thread.currentThread().sleep(2000);
receiver.close();
}
catch(Exception ex) {
System.err.println(“Exception in PTPListener: “ + ex);
}
}
A sleep has been placed in the main body to allow time for messages to be handled.
Normally, the main() method would sleep for a long period or do other processing and
therefore requires some way to determine when message processing is finished.
Typically, a shutdown message is sent or the user interface is used to close the application. For brevity, this code is not included here.
Asynchronous Exception Handling
If you register an ExceptionListener with a connection, your application will be asynchronously informed of problems. If a client only consumes a message, this may be the
only way it can be informed that the connection has failed.
The JMS provider will call the listener’s onException() method, passing it a
JMSException describing the problem.
The Publish/Subscribe Message Domain
So far, you have seen how to create a simple point-to-point application with a single
sender and receiver. Now you will build a simple bulletin board application to demonstrate the features of the publish/subscribe model.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 416
416
Day 9
For this example, a TopicPublisher will be produced that creates messages and publishes them to a bulletin board. A single TopicSubscriber will asynchronously listen for
messages. The messages will be printed to the screen—one at time—in the order they
were received. The program then exits.
Remember that, unlike messages in a queue, messages in a topic are immediately distributed to all subscribers. As a result, the timing of the publisher and the subscriber
becomes important. Apart from this, the publisher/subscriber code is very similar to the
sender/receiver code. In fact, the publisher code is a copy of the previous sender code
with all references to Queue<object> changed to Topic<object>.
Table 9.5 lists the JMS objects in the publish/subscribe domain.
TABLE 9.5
Components in the JMS Publish/Subscribe Message Domain
Component
Description
TopicConnectionFactory
Administered object used to create an object that implements the
TopicConnection interface
TopicConnection
Active connection to a JMS provider
TopicSession
Provides methods for creating objects that implement the
TopicPublisher and TopicSubscriber interfaces
TopicPublisher
Used by a client to publish a message to a topic
TopicSubscriber
Used to receive messages sent to a topic
Durable TopicSubscriber
Used to receive messages published when the subscriber is inactive
Topic
Encapsulates the JMS provider topic name
Because subscribers only receive messages when they are active, it would be nice to be
able to test for active subscribers before publishing to a topic. Unfortunately, this is not
possible.
Publish/Subscribe Messaging Example
Now, you will build a simple bulletin board application. For this example, the bulletin
board publisher program will generate 10 simple messages. The subscriber will be a
Swing application that will display the messages as they arrive.
The bulletin board used a topic called jms/bulletinBoard. This must be created using
the Configure Installation screen in deploytool or using j2eeadmin as follows:
j2eeadmin –addJMSDestination jms/bulletinBoard topic
12 0672323842 CH09
3/20/02
9:23 AM
Page 417
Java Message Service
417
Bulletin Board Publisher
The same mechanism is used to create a topic as a queue, so Listing 9.3 should appear
very similar to that in the point-to-point receiver example, except that all references to a
queue are replaced with topic.
LISTING 9.3
Bulletin Board Publisher Program
1: import javax.naming.*;
2: import javax.jms.*;
3:
4: public class BulletinBoardPublisher {
5:
private TopicConnection topicConnection;
6:
private TopicSession topicSession;
7:
private TopicPublisher bulletinBoardPublisher;
8:
private Topic bulletinBoard;
9:
10:
public static void main(String[] args) {
11:
try {
12:
BulletinBoardPublisher publisher = new
BulletinBoardPublisher(“TopicConnectionFactory”,”jms/bulletinBoard”);
13:
14:
System.out.println (“Publisher is up and running”);
15:
16:
for (int i = 0; i < 10; i++) {
17:
String bulletin = “Bulletin Board Message number: “ + i;
18:
System.out.println (bulletin);
19:
publisher.publishMessage(bulletin);
20:
}
21:
publisher.close();
22:
} catch(Exception ex) {
23:
System.err.println(“Exception in BulletinBoardPublisher: “ +
ex);
24:
}
25:
}
26:
27:
public BulletinBoardPublisher(String JNDIconnectionFactory,
➥ String JNDItopic) throws JMSException, NamingException {
28:
Context context = new InitialContext();
29:
TopicConnectionFactory topicFactory = (TopicConnectionFactory)context.lookup(JNDIconnectionFactory);
30:
topicConnection = topicFactory.createTopicConnection();
31:
topicSession = topicConnection.createTopicSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
32:
bulletinBoard = (Topic)context.lookup(JNDItopic);
33:
bulletinBoardPublisher =
➥ topicSession.createPublisher(bulletinBoard);
34:
}
35:
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 418
418
Day 9
LISTING 9.3
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47: }
Continued
public void publishMessage(String msg) throws JMSException {
TextMessage message = topicSession.createTextMessage();
message.setText(msg);
bulletinBoardPublisher.publish(message);
}
public void close() throws JMSException {
bulletinBoardPublisher.close();
topicSession.close();
topicConnection.close();
}
Run this program from the command line. This will check that this program runs okay,
but remember that messages published to topics are not persistent. For the subscriber
program to pick up the messages, you will need to run this program again while the subscriber is running.
Bulletin Board Subscriber
The subscriber is a Swing application that outputs the bulletins as they arrive (see Listing
9.4).
Remember that for this program to receive the bulletins, it must be running when they
are published.
LISTING 9.4
Bulletin Board Subscriber Program
1: import javax.naming.*;
2: import javax.jms.*;
3: import java.io.*;
4: import javax.swing.*;
5: import java.awt.*;
6: import java.awt.event.*;
7: public class BulletinBoardSubscriber extends JFrame
➥ implements MessageListener {
8:
private TopicConnection topicConnection;
9:
private TopicSession topicSession;
10:
private TopicSubscriber bulletinBoardSubscriber;
11:
private Topic bulletinBoard;
12:
private JTextArea textArea = new JTextArea(4,32);
13:
14:
public static void main(String[] args) {
15:
try {
16:
final BulletinBoardSubscriber subscriber = new
➥ BulletinBoardSubscriber(“jms/TopicConnectionFactory”,”jms/bulletinBoard”);
12 0672323842 CH09
3/20/02
9:23 AM
Page 419
Java Message Service
LISTING 9.4
419
Continued
17:
subscriber.addWindowListener(new WindowAdapter() {
18:
public void windowClosing(WindowEvent ev) {
19:
try {
20:
subscriber.close();
21:
} catch(Exception ex) {
22:
System.err.println(“Exception in
➥ BulletinBoardSubscriber: “ + ex);
23:
}
24:
subscriber.dispose();
25:
System.exit(0);
26:
}
27:
});
28:
subscriber.setSize(500,400);
29:
subscriber.setVisible(true);
30:
} catch(Exception ex) {
31:
System.err.println(“Exception in BulletinBoardSubscriber: “
➥ + ex);
32:
}
33:
}
34:
35:
public BulletinBoardSubscriber(String JNDIconnectionFactory,
➥ String JNDItopic) throws JMSException, NamingException {
36:
super (JNDIconnectionFactory+”:”+JNDItopic);
37:
getContentPane().add(new JScrollPane(textArea));
38:
Context context = new InitialContext();
39:
TopicConnectionFactory topicFactory = (TopicConnectionFactory)context.lookup(JNDIconnectionFactory);
40:
topicConnection = topicFactory.createTopicConnection();
41:
topicSession = topicConnection.createTopicSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
42:
bulletinBoard = (Topic)context.lookup(JNDItopic);
43:
bulletinBoardSubscriber =
➥ topicSession.createSubscriber(bulletinBoard);
44:
bulletinBoardSubscriber.setMessageListener(this);
45:
topicConnection.start();
46:
}
47:
48:
public void onMessage(Message message) {
49:
try {
50:
if (message instanceof TextMessage) {
51:
String bulletin = ((TextMessage) message).getText();
52:
String text = textArea.getText();
53:
textArea.setText(text+”\n”+bulletin);
54:
}
55:
} catch(JMSException ex) {
56:
System.err.println(“Exception in
➥ BulletinBoardSubscriber:OnMessage: “ + ex);
57:
}
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 420
420
Day 9
LISTING 9.4
58:
59:
60:
61:
62:
63:
64:
65:
66: }
Continued
}
public void close() throws JMSException {
bulletinBoardSubscriber.close();
topicSession.close();
topicConnection.close();
}
When you run this program from the command line, a small window will appear. Any
messages published to the bulletin board topic while the program is running will appear
in this window.
Creating Durable Subscriptions
When you run the bulletin board example, you will have seen that you need to get the
timing right and that the subscriber can miss bulletins if it is not running when they are
sent. This is because the TopicSession.createSubscriber() method creates a nondurable subscriber. A non-durable subscriber can only receive messages that are published while it is active.
To get around this restriction, the JMS API provides a
method. With a durable subscription, the
JMS provider stores the messages published to the topic, just as it would store messages
sent to a queue.
TopicSession.createDurableSubscriber()
Figure 9.8 shows diagrammatically how messages are consumed with non-durable and
durable subscriptions when the subscriber is inactive during the period when messages
are published.
To create a durable subscription, you must associate a connection factory with a defined
user and use this factory to create the connection. You will be shown how to create users
on day 15, “Security,” but for now, you can use the user guest that has been set up for you.
Use the following command to associate a connection factory with the user guest:
j2eeadmin -addJmsFactory jms/DurableTopic topic -props clientID=guest
or you can add the factory using the Configure Installation screen in install tool. Select
Connection Factories in the panel on the left. Add the new factory, jms/DurableTopic, to
the panel on the top right and add ClientID as the Property Name with Value of guest in
the panel at bottom right.
12 0672323842 CH09
3/20/02
9:23 AM
Page 421
Java Message Service
FIGURE 9.8
Non-durable and
durable subscriptions.
421
Non-durable Subscription
Publisher
Subscriber
Messages
received
Msg
1
Msg
2
Msg
3
Msg
4
active
Msg
1
Msg
5
Msg
6
active
Msg
2
Msg
5
Msg
6
Msg
5
Msg
6
Durable Subscription
Publisher
Subscriber
Messages
received
Msg
1
Msg
2
active
Msg
1
Msg
2
Msg
3
Msg
4
active
Msg
Msg
3
5
Msg
Msg
4
6
After using this connection factory to create the connection and session, you call the
createDurableSubscriber method with two arguments, the topic and the subscription
ID string that specifies the name of the subscription:
String subID = “DurableBulletins”;
TopicSubscriber topicSubscriber =
➥ topicSession.createDurableSubscriber(bulletinBoard, subID);
Messages are then read from the topic as normal. To temporarily stop receiving messages, you simply close the subscriber.
topicSubscriber.close();
Messages are now stored by the JMS provider until the subscription is reactivated with
another call to createDurableSubscriber() with the same subscription ID.
A subscriber can permanently stop receiving messages by unsubscribing a durable subscription with the unsubscribe() method. You first need to close the subscriber.
topicSubscriber.close();
topicSession.unsubscribe(subID);
If you make these changes to the bulletin board subscriber program, you will not initially
notice any difference in operation. The distinction becomes apparent if you close the subscriber program and run the publisher. Now when you start up the durable subscriber
once more, you will receive messages sent to the bulletin board while the program was
not running.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 422
422
Day 9
Additional JMS Features
The following sections cover some additional features available in JMS. Not all the features of JMS are covered, and you should refer to the JMS API specification for more
information.
Message Selectors
So far, you have received all the messages sent. The JMS API provides support for filtering received messages. This is accomplished by using a message selector. The
createReceiver and the two forms of createSubscriber (durable and nondurable)
methods all have a signature that allows a message selector to be specified.
The message selector is a string containing an SQL conditional expression. Only message header values and properties can be specified in the message selector. Sadly, it is
not possible to filter messages on the basis of the contents of the message body.
String highPriority = “JMSPriority = ‘9’ AND topic = ‘Java’”;
bulletinBoardSubscriber = topicSession.createSubscriber(bulletinBoard,
➥ highPriority, false);
This selector will ensure that only priority nine messages are received. Note here that
topic is a property of the message that has been created and set by the sender.
Notice that for this form of the createSubscriber the parameters are as follows:
TopicSession.createSubscriber(topic, messageSelector, noLocal);
You need to set the noLocal parameter to specify whether you want to receive messages
created by your own connection. Set noLocal to false to prevent the delivery of messages created by the subscriber’s own connection.
Session Acknowledgement Modes
In the examples given so far, auto acknowledgement has been used to send the acknowledgement automatically as soon as the message is received. This has the advantage of
removing the burden of acknowledging messages from you, but it has the disadvantage
that if your application fails before the message is processed, the message may be lost.
After a message is acknowledged, the JMS provider will never redeliver it.
Deferring acknowledgement until after you have processed the message will protect
against loss of data. To do this, the session must be created with client acknowledgement.
queueConnection.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
12 0672323842 CH09
3/20/02
9:23 AM
Page 423
Java Message Service
423
Now when the message is received, no acknowledgement will be sent automatically. It is
up to you to ensure that the message is acknowledged at some later point.
message = (TextMessage) queueReceiver.receive();
// process the message
message.acknowledge();
If you do not acknowledge the message, it may be resent.
A third acknowledgement mode, DUPS_OK_ACKNOWLEDGE, can be used when the delivery
of duplicates can be tolerated. This is a form of AUTO_ACKNOWLEDGE that has the advantage of reducing the session overhead spent preventing the delivery of duplicate messages.
Message Persistence
The default JMS delivery mode for a message is PERSISTENT. This ensures that the message will be delivered, even if the JMS provider fails or is shut down.
A second delivery mode, NON_PERSISTENT, can be used where guaranteed delivery is not
required. A NON_PERSISTENT message has the lowest overhead because the JMS provider
does not need to copy the message to a stable storage medium. JMS still guarantees to
deliver a NON_PERSISTENT message at most once (but maybe not at all). Nonpersistent
messages should be used when:
• Performance is important and reliability is not
• Messages can be lost with no effect on system functionality
Persistent and non-persistent messages can be delivered to the same destination.
Transactions
Often, acknowledgement of single messages is not enough to ensure the integrity of an
application. Think of a banking system where two messages are sent to debit an amount
from one account and credit the same amount to another. If only one of the messages is
received, there will be a problem. A transaction is required where a number of operations
involving many messages forms an atomic piece of work.
In JMS, you can specify that a session is transacted when a session queue or topic is created:
createQueueSession(boolean transacted, int acknowledgeMode)
createTopicSession(boolean transacted, int acknowledgeMode)
In a transacted session, several sends and receives are grouped together in a single transaction. The JMS API provides Session.commit() to acknowledge all the messages in a
transaction and Session.rollback() to discard all messages. After a rollback, the messages will be redelivered unless they have expired.
9
12 0672323842 CH09
3/20/02
9:23 AM
424
Page 424
Day 9
To create a transacted queue session, set the transacted parameter to true, as shown in
the following:
topicSession = topicConnection.createTopicSession(true, 0);
For transacted sessions, the acknowledgeMode parameter is ignored. The previous code
sets this parameter to 0 to make this fact explicit.
There is no explicit transaction start. The contents of a transaction are simply those messages that have been produced and consumed during the current session, either since the
session was created or since the last commit(). After a commit() or rollback(), a new
transaction is started.
Note
Because the commit() and rollback() methods are associated with a session,
it is not possible to mix messages from queues and topics in the same transaction.
The following example shows a simple transaction involving two messages.
queueSession = queueConnection.createQueueSession(true, 0);
Queue bank1Queue = (Queue)context.lookup(“queue/FirstUSA”);
Queue bank2Queue = (Queue)context.lookup(“queue/ArabBank”);
bank1QueueSender = queueSession.createSender(bank1Queue);
bank2QueueSender = queueSession.createSender(bank2Queue);
// .. application processing to create debit and credit messages
try {
bank1QueueSender.send(bank1Queue, debitMsg);
bank2QueueSender.send(bank2Queue, creditMsg);
queueSession.commit();
} catch(JMSException ex) {
System.err.println(“Exception in bank transaction:” + ex);
queueSession.rollback();
}
Where a receiver handles atomic actions sent in multiple messages, it should similarly
only commit when all the messages have been received and processed.
XA support
A JMS provider may provide support for distributed transaction using the X/Open XA
resource interface. This is performed by utilizing the Java Transaction API (JTA). JTA
was covered on Day 8, “Transactions and Persistence”. XA support is optional; refer to
your JMS provider documentation to see if XA support is provided.
12 0672323842 CH09
3/20/02
9:23 AM
Page 425
Java Message Service
425
Multithreading
Not all the objects in JMS support concurrent use. The JMS API only specifies that the
following objects can be shared by across multiple threads
• Connection Factories
• Connections
• Destinations
Many threads in the same client may share these objects, whereas the following:
• Sessions
• Message Producers
• Message Consumers
can only be accessed by one thread at a time. The restriction on single-threaded sessions
reduces the complexity required by the JMS provider to support transactions.
Session concurrency can be implemented within a multithreaded client by creating multiple sessions.
Introduction to XML
Among the five message body types supported in the JMS API, the TextMessage type
was included on the presumption that String messages will be used extensively. The reason for this presumption is the increasing use of the Extensible Markup Language
(XML) for inter-application communication.
What Is XML and Why Would You Use It?
XML is a structured text-based language consisting of tags that are used to describe a
document’s structure and meaning. It does not say anything about the visual representation of the document.
XML is non-proprietary. It is also easy for both computers and people to understand.
Consequently, it is an extremely useful format for the interchange of data between different applications. By defining not only the content but also the structure of the data, XML
is ideally suited to manipulating arbitrary data structures.
There are many good reasons for using XML, but one of the most significant is its ability
to adapt to changes in the data. Because of this, senders and receivers do not need to
agree on a common data format ahead of time.
9
12 0672323842 CH09
3/20/02
9:23 AM
Page 426
426
Day 9
The following is an example of XML:
<person>
<name>
<firstname>Winston</firstname>
<surname>Churchill</surname>
</name>
<birth>
<date month=’November’ day=’30’ year=’1874’</date>
<place>Bleinham Palace, England</place>
</birth>
<death>
<date> month=’January’ day=’24’ year=’1965’</date>
<buried>Bladon, Oxfordshire, </buried>
</death>
</person>
Even with no knowledge of XML, it is easy to work out what this example is describing;
and herein lies much of the power of XML.
XML will be covered in more detail on Day 16, “ Integrating XML with J2EE” and an
overview is provided in Appendix C, “An Overview of XML,” on the CD-ROM.
Summary
Today, you have had an introduction to JMS messaging, the concept of message producers and consumers, and explored the two supported message domains—point-to-point
and publish/subscribe. This has necessarily been an overview of JMS. More information
should be obtained from the latest JMS specification and API. Also, refer to the documentation for your JMS provider to determine what features beyond those described here
are supported.
You have also been given a brief introduction to XML and seen how it can be used to
communicate with other applications.
Tomorrow, you will utilize your JMS knowledge gained today while examining the third
type of EJB—Message-driven beans.
Q&A
Q What type of JMS message domain should be used to send a message to a
single receiver?
A A point-to-point domain is the appropriate choice in this scenario.
12 0672323842 CH09
3/20/02
9:23 AM
Page 427
Java Message Service
427
Q What type of JMS message domain should be used to send a message to many
receivers at the same time?
A To send to many receivers, the publish/subscribe message domain is the best
choice.
Q What is the difference between JMSHeader fields and JMSProperty fields?
A JMS header fields are defined in the JMS API and are mainly set by the JMS
provider. JMS property fields are used by clients to add additional header information to a message.
Q Does JMS guarantee to deliver a message in the point-to-point domain?
A Messages in the point-to-point domain are PERSISTENT by default and will be
delivered unless they have a timeout that has expired. Point-to-point messages can
be set to NON_PERSISTENT, in which case, the message may be lost if a provider
fails.
Q When should I use a durable subscription?
A Durable subscriptions should be used when a subscriber needs to receive messages
from a topic when it is inactive.
Exercise
To extend your knowledge of the subjects covered today, try the following exercises.
1. Create a chat room application. Participants provide their name and can send messages to any topic (hint: use a JMS property to define the topic). Participants may
read messages posted by all other participants or filter by topic. You may use predefined topic names.
To assist you in this task, three Java files have been provided in the exercise sub-directory for Day 9 on the accompanying CD-ROM.
The Chat.java and ChatDisplay.java files are complete and need not be edited. These
files provide the Swing code to enter and display the chat room messages onscreen.
The TopicServer.java is a starting point for you to further develop the chat server. The
initial code simply uses the callback method addMessage to bounce the message back to
the screen. The addMessage method uses the interface defined in ChatDisplay.java.
You will need to edit this file to replace this callback with code to publish the message to
a topic. You then need to add a subscriber that consumes messages from this topic and
displays them onscreen.
9
12 0672323842 CH09
428
3/20/02
9:23 AM
Page 428
Day 9
Add a property called From to the message and set it to the from parameter passed in.
This will then be displayed in the chat room window.
A completed TopicServer is included in the solutions sub-directory of Day 9 of the
case study.
13 0672323842 CH10
3/20/02
9:28 AM
Page 429
WEEK 2
DAY
10
Message-Driven Beans
So far, you have looked at two types of Enterprise Java Bean (EJB)—the
Session bean and the Entity bean. Today you will consider the third and final
EJB, the Message-driven bean. Topics that are covered are as follows:
• Similarities and differences with Entity and Session beans
• The life-cycle of a Message-driven bean
• Writing a Message-driven bean
Prior to the EJB 2.0 specification, it was only possible to support asynchronous
message passing by writing an external Java program that acted as a listener. A
listener is a program whose sole purpose is to wait for data to arrive, for example, a socket server program that “listens” on a socket and perform some action
when it detects client connections. The listener was then able to invoke methods
on a session or entity bean. All EJB method calls had to be synchronous and
initiated by the client. This approach had the disadvantage that the message was
received outside of the server, so it could not be part of an EJB transaction.
With the release of J2EE 1.3, you can use Message-driven beans to combine the
functionality of EJBs with the Java Message Service (JMS).
13 0672323842 CH10
430
3/20/02
9:28 AM
Page 430
Day 10
Although JMS was covered in detail on Day 9, “Java Message Service,” the following is
a quick recap of its main features:
• JMS is a Java API that specifies how applications can create, send, receive, and
read messages.
• JMS enables communication that is both asynchronous and reliable, while minimizing the amount of knowledge and programming that is required.
• The implementation of the JMS API is provided by a number of vendors who are
known as JMS providers.
• Message queues are associated with the point-to-point message domain. Messages
in a queue are persistent but can only be consumed by one receiver.
• Topics allow a message to be sent to more then one receiver (called a subscriber).
Messages are not persistent; they are immediately delivered to all existing subscribers.
What Are Message-Driven Beans?
Message-driven beans are generally constructed to be message consumers, although they
can, like any other EJB, also be used to create and send messages. A Message-driven
bean lives entirely within the container, it has no security context of its own. When the
bean is deployed, it is associated with a particular queue or topic, and is invoked by the
container when a message arrives for that queue or topic.
The following are the features of a Message-driven bean:
• It is anonymous; that is, it has no client visibility. No state is maintained for the
client.
• All instances of a particular Message-driven bean are equivalent.
• The container can pool instances.
• It does not have a local or remote interface.
• It is invoked asynchronously by the container.
• The bean lives entirely within a container; the container manages its lifecycle and
environment.
These features are discussed in more detail next.
The Message Producer’s View
To the client producing JMS messages, the Message-driven bean is just an anonymous
message consumer. The client need not be aware that the consumer is a Message-driven
13 0672323842 CH10
3/20/02
9:28 AM
Page 431
Message-Driven Beans
431
bean. The client simply sends its messages to a destination, either a queue or a topic, and
the bean handles the message when it arrives. Therefore, the coding of message producers in an application using Message-driven beans is exactly the same as any JMS application; that is, the message must conform to the JMS specification and the destination must
be a Java Naming and Directory Interface (JNDI) registered name. Apart from this, the
message does not have to correspond to any particular format.
It is not necessary for the client to be a Java client application or an EJB to take advantage of Message-driven beans; it can be a Java ServerPagesTM (JSP) component or a
non-J2EE application.
Similarities and Differences with Other EJBs
In some respects, a Message-driven bean is similar to a stateless Session bean. It is a
complete EJB that can encapsulate business logic. An advantage is that the container is
responsible for providing functionality for security, concurrency, transactions, and so
forth. Like a Session or Entity bean, a Message-driven bean has a bean class and XML
deployment descriptor.
The main difference from the other EJBs is that a Message-driven bean cannot be called
directly by the client. For this reason, they do not have Home, Remote, or Local interfaces,
this makes them less prone to misuse by the client.
Unlike Entity and Session beans, Message-driven beans do not have a passive state.
Therefore, they do not implement the ejbActivate() and ejbPassivate() methods.
Note
Although a Message-driven bean is considered to be a stateless object, from
the client’s view, it can and should retain state in its instance variables.
Examples of this are an open database connection and the Home, Local, and
Remote interfaces to other EJBs.
Programming Interfaces in a Message-Driven
Bean
There are a number of constraints on the contents of a Message-driven bean class. In particular your Message-driven bean class must
• Implement the javax.ejb.MessageDrivenBean interface
• Implement the javax.jms.MessageListener interface
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 432
432
Day 10
• Have a single constructor, with no arguments
• Have a single public setMessageDrivenContext(MessageDrivenContext
method that returns a void
ctx)
• Have a single public ejbCreate() method with no arguments that returns a void
• Have a single public ejbRemove() method with no arguments that returns a void
• Have a single public onMessage(Message
message)
method that returns a void
• Not have a finalize() method
The following sections cover these methods in more detail.
Life Cycle of a Message-Driven Bean
The EJB container controls the lifecycle of a Message-driven bean. The Message-driven
bean instance lifecycle has three states, as shown in Figure 10.1:
• Does Not Exist—The Message-driven bean has not been instantiated yet or is
awaiting garbage collection.
• Method Ready Pool—A pool of Message-driven bean instances, similar to the
instance pool used for stateless session beans.
• Processing a message—The Message-driven bean has been instantiated and is handling a message.
FIGURE 10.1
The Message-driven
bean life cycle.
Message-driven
Bean
setMessageDrivenContext()
ejbCreate()
State: in Method
Ready Pool
onMessage()
do/process
message
13 0672323842 CH10
3/20/02
9:28 AM
Page 433
Message-Driven Beans
433
After constructing the new instance of the Message-driven bean object, the container
invokes the following methods:
• The bean’s setMessageDrivenContext() method with a reference to its EJB context. The Message-driven bean should store its MessageDrivenContext reference in
an instance field.
• The bean’s ejbCreate() method. The Message-driven bean’s ejbCreate() method
takes no arguments and is invoked only once when the bean is first instantiated.
The Message-Driven Bean Context
The javax.ejb.MessageDrivenContext interface (see the class diagram in Figure 10.2).
provides the Message-driven bean with access to its runtime context. This is similar to
the SessionContext and EntityContext interfaces for Session and Entity beans.
FIGURE 10.2
EJBContext
The
MessageDrivenContext
class diagram.
MessageDrivenContext
getCallerPrincipal()
getEJBHome()
getEJBLocalHome()
getRollbackOnly()
getUserTransaction()
isCallerInRole()
setRollbackOnly()
Note that all the EJBContext methods are available to a Message-driven bean, but
because the Message-driven bean does not have a local or remote interface, calls to
getEJBHome() and getEJBLocalHome() will throw a
java.lang.IllegalStateException.
Because Message-driven beans are anonymous and run within the context of the container, and the container does not have a client security identity or role, calls to the
getCallerPrincipal() and IsCallerInRole() methods will also cause an
IllegalStateException.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 434
434
Day 10
Creating a Message-Driven Bean
The setMessageDrivenContext() method can throw EJBException if there is a container or system level error of some kind. See the section called “Handling Exceptions” for
more details. What follows is an example setMessageDrivenContext() method that saves
its EJBContext and JNDI context:
private MessageDrivenContext mdbContext;
private Context jndiContext;
public void setMessageDrivenContext (MessageDrivenContext ctx) {
mdbContext = ctx;
try { jndiContext = new InitialContext();
} catch (NamingException nameEx) {
throw new EJBException(nameEx);
}
}
After calling setMessageDrivenContext(), the container calls the bean’s ejbCreate()
method, which takes no parameters. You could use this method to allocate resources,
such as a datasource, but in practice, this is usually done in the
setMessageDrivenContext() method. Therefore, it is normal to find the ejbCreate()
method empty.
This method is only invoked when the bean instance is first created.
public void ejbCreate () throws CreateException
After the ejbCreate() method has been called, the bean is placed in the method-ready
pool.
Method-Ready Pool
The actual point at which Message-driven bean instances are created and placed in the
method-ready pool is vendor specific. The vendor of an EJB server could design it to
only create Message-driven bean instances when they are required. Alternatively, when
the EJB server is started, a number of instances may be placed in the method-ready pool
awaiting the first message. Additional instances can be added to the pool when the number of Message-driven beans is insufficient to handle the number of incoming messages.
Therefore, the life of a Message-driven bean instance could be very long and, in this
case, it makes sense to adopt an approach where you retain state (such as an open database connection) across the handling of several messages. However, the container may
create and destroy instances to service every incoming message. If this is the case, this
approach is no longer efficient. Check your vendor’s documentation for details on how
your EJB server handles Message-driven bean instances in the method-ready pool.
13 0672323842 CH10
3/20/02
9:28 AM
Page 435
Message-Driven Beans
435
Message-driven bean instances in the method-ready pool are available to consume
incoming messages. Any available instance can be allocated to a message and, while processing the message, this particular bean instance is not available to consume other messages. A container can handle several messages concurrently by using a separate instance
of the message bean for each message. Each separate instance obtains its own
MessageDrivenContext from the container. After the message has been processed, the
instance is available to consume other messages. Message-driven beans are always
single-threaded objects.
The Demise of the Bean
When the server decides to reduce the total size of the method-ready pool, a bean
instance is removed from the pool and becomes available for garbage collection. At this
point, the bean’s ejbRemove() method is called.
You should use this method to close or deallocate resources stored in instance variables
and set the instance variable to null.
public void ejbRemove()
The EJBException can be thrown by ejbRemove() to indicate a system-level error.
Following ejbRemove(), the bean is dereferenced and no longer available to handle messages. It will eventually be garbage collected.
Note
The ejbRemove() method may not be called if the Message-driven bean
instance throws an exception. This could result in resource leaks.
A Message-driven bean must not define the finalize method to free up resources: do all
the tidying up in ejbRemove().
Consuming Messages
When a message is received, the container finds a Message-driven bean instance that is
registered for that queue or topic and calls the bean’s onMessage() method.
public void onMessage(Message message)
This method has a single parameter that contains a single JMS message. The message
will have a header, one or more properties (optional), and a message body (consisting of
one of the five JMS message body types). JMS messages were covered in some detail on
Day 9.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 436
436
Day 10
The Message-driven bean must provide a single onMessage() method, and this method
should not throw runtime exceptions. It must not have a throws clause as part of its
method signature. The onMessage() holds the business logic of the bean. You can use
helper methods and other EJBs to process the message.
Remember, Message-driven bean instances are triggered asynchronously; the business
logic within the bean must reflect this. You must never presume any ordering to the messages received. Even if the system is implemented within the same JVM, the system
vagaries can cause the scheduling of bean instances to be non-deterministic, this means
that you cannot ascertain or control when the bean will run.
Handling Exceptions
The Message-driven bean can encounter various exceptions or errors that prevent it from
successfully completing. The following are examples of such exceptions:
• Failure to obtain a database connection
• A JNDI naming exception
• A RemoteException from invocation of another EJB
• An unexpected RuntimeException
A well-written Message-driven bean should never carelessly throw a RunTimeException.
If a RunTimeException is not caught in onMessage() or any other bean class method, the
container will simply discard the instance (it will transition it to the Does Not Exist
state). In this case, the container will not call the ejbRemove() method, so a badly written bean method could cause resource leaks.
Obviously, you need a mechanism to tell the container that you have caught an unrecoverable error and die gracefully. To do this, you use exception layering. You catch the
RunTimeException, free up resources, do any other appropriate processing and then
throw an EJBException to the container. The container will then log the error, rollback
any container-managed transactions, and discard the instance.
Because identical Message-driven bean instances are available, from the client perspective, the message bean continues to exist in the method-ready pool to handle further messages. Therefore, a single instance failure may not cause any disruption to the system.
Container- and Bean-Managed Transactions
The analysis of container- versus bean-managed transactions was covered as part of Day
8’s material, reread this if you need to recap the benefits of either method of handling
transactions. When designing your Message-driven bean, you must decide whether the
13 0672323842 CH10
3/20/02
9:28 AM
Page 437
Message-Driven Beans
437
bean will demarcate the transactions programmatically (bean managed transactions), or if
the transaction management is to be performed by the container. This is done by setting
the transaction-type in the deployment descriptor.
<transaction-type>Container</transaction-type>
Use container-managed transactions unless you have some other reason for using beanmanaged transactions, such as creating and sending a series of messages.
A Message-driven bean can be designed with either bean-managed transactions or with
container-managed transactions, but both cannot be used in the same bean.
The following methods in the javax.ejb.MessageDrivenContext (all inherited from
javax.ejb.EJBContext) can be used with transactions.
public UserTransaction getUserTransaction() throws
java.lang.IllegalStateException
The UserTransaction interface methods can be used to demarcate transactions.
The getUserTransaction() method can only be called if the Message-driven bean is
using bean-managed transactions. An attempt to use this method from a bean using container-managed transactions will cause a java.lang.IllegalStateException to be
thrown.
Both the methods setRollbackOnly() and getRollbackOnly() can only be used with
container-managed transactions. This time, the IllegalStateException will be thrown
if they are utilized in the context of a bean-managed transaction.
public void setRollbackOnly() throws java.lang.IllegalStateException
Typically, you use setRollbackOnly() after an exception or error of some kind to mark
the current transaction to be rolled back.
public boolean getRollbackOnly() throws java.lang.IllegalStateException
The getRollbackOnly() method returns true if the transaction has been marked for rollback; otherwise, it returns false. You usually call this method after an exception has
been caught to see if there is any point in continuing working on the current transaction.
Message Acknowledgment
With Message-driven beans, the container handles message acknowledgement. The
default being AUTO_ACKNOWLEDGE.
If you use container-managed transactions, you have no control over the message
acknowledgement; it is done automatically as part of the transaction commit.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 438
438
Day 10
With bean-managed transactions, you can specify DUPS_OK_ACKNOWLEDGE as an alternative to the default. To do this, set the acknowledge-mode element in the deployment
descriptor. With DUPS_OK_ACKNOWLEDGE set, you can reduce the session overhead spent
preventing delivery of duplicate messages, but only do this if receiving duplicate messages will not cause a problem with the business logic of your bean.
<transaction-type>Bean</transaction-type>
<acknowledge-mode>Dups-ok-acknowledge</acknowledge-mode>
JMS Message Selectors
JMS message selectors was covered in detail on Day 9. A message selector is a string
containing an SQL conditional expression. Only JMS message header values and message properties can be specified in the message selector.
With Message-driven beans, the selector is specified at deployment time.
The message selector is added to the screen in the Deployment Tool (see Figure 10.3). In
the example shown, the bean will handle only messages that have a JMSPriority greater
than the default of 4.
FIGURE 10.3
Deployment Tool
screen showing the setting of message selectors.
The deployment descriptor is updated to include the message-selector tag.
<message-selector>JMSPriority &gt;4</message-selector>
13 0672323842 CH10
3/20/02
9:28 AM
Page 439
Message-Driven Beans
439
Writing a Simple Message-Driven Bean
As you work through this section, you will create a Message-driven bean that that simply
prints out the contents of a text message on screen.
So that the Message-driven bean can work asynchronously you will employ the
MessageListener interface. This interface and the associated onMessage() method,
which is invoked each time a message is available at the destination, were fully described
in Day 9.
Implementing the Interfaces
As already stated, all Message-driven beans must implement the MessageDrivenBean and
MessageListener interfaces.
import javax.ejb.*;
import javax.jms.*;
public class MDBPrintMessage implements MessageDrivenBean, MessageListener {
// class body not shown – see listing 10.1
}
Just like the EntityBean and SessionBean interfaces, the MessageDrivenBean interface
extends the javax.ejb.EnterpriseBean interface.
The MessageDrivenBean interface contains only two methods—
and ejbRemove(), see the class diagram in Figure 10.4.
setMessageDrivenContext()
FIGURE 10.4
EnterpriseBean
The
MessageDrivenBean
class diagram.
MessageDrivenBean
setMessageDrivenContext()
ejbRemove()
You also need to supply an ejbCreate() method.
In this example, we have no need to create or store resources, and it is so simple that we
will leave all the required methods blank.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 440
440
Day 10
public void setMessageDrivenContext (MessageDrivenContext ctx) {}
public void ejbRemove() {}
public void ejbCreate() {}
The MessageListener interface is where the Message-driven bean carries out the bean’s
business logic. As already stated, it consists of the single method onMessage(). In this
example, we will simply test that the message is a TextMessage and, if it is, print it to
the screen. The full code is shown in Listing 10.1.
LISTING 10.1
Simple Print Message Message-Driven Bean
1: import javax.ejb.*;
2: import javax.jms.*;
3:
4: public class MDBPrintMessage implements MessageDrivenBean, MessageListener {
5:
6:
public void setMessageDrivenContext (MessageDrivenContext ctx) {}
7:
public void ejbRemove() {}
8:
public void ejbCreate() {}
9:
10:
public void onMessage(Message message) {
11:
try {
12:
if (message instanceof TextMessage)
13:
{
14:
String text = ((TextMessage) message).getText();
15:
System.out.println(“Received: “ + text);
16:
}
17:
} catch(Exception ex) {
18:
throw new EJBException(ex);
19:
}
20:
}
21: }
As you can see, there is no reference in this code to any particular JMS queue or topic.
This means that the bean is not only generic and can be associated with any queue or
topic at deployment time, it can also be associated with several different queues or topics
at the same time.
Running the Example
Before you can see your Message-driven bean working, there are a number of steps still
to go through:
1. Compile the bean.
2. Use j2eeadmin or deploytool to create the message queue.
13 0672323842 CH10
3/20/02
9:28 AM
Page 441
Message-Driven Beans
441
3. Deploy the bean.
4. Create a sender client to create a message.
Creating the Queue
The message bean is associated with a queue or topic at deployment time. This queue
must already exist. To create a queue (or topic), do the following:
1. Ensure that J2EE server is running.
2. Use j2eeadmin or deploytool to create the message queue or a topic.
To see the existing queues and topics, use the following:
j2eeadmin –listJMSDestination
or view the Destinations screen in deploytool. This is found by selecting Server
Configuration from the Tools menu and then the Destinations icon in the left panel.
The J2EE RI has two default queues predefined—jms/Queue and jms/Topic.
To add your queue, use the following:
j2eeadmin –addJMSDestination jms/firstQueue queue
The j2eeadmin command works silently, so to check that your queue has been created,
run J2eeadmin -listJMSDestination once more. Figure 10.5 demonstrates the use of
these two commands to create a queue called jms/firstQueue (this is the queue you will
use in this first example).
Alternatively, you can add the queue in deploytool on the Destinations screen.
FIGURE 10.5
Using j2eeadmin to
add a JMS queue to
the container.
When your bean is deployed, the following will appear in the XML deployment
descriptor.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 442
442
Day 10
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
</message-driven-destination>
Deploying the Message-Driven Bean
By this time, you should be familiar with deploying Entity and Session beans. This section will only cover in any detail where the process differs for Message-driven beans.
The steps are as follows:
1. Run the deploytool.
2. Create a new application to hold your bean called MDBPrintMessage.
3. Select New Enterprise Bean from the File menu and add the
MDBPrintMessage.class file to the MDBPrintMessage application JAR file.
4. On the next screen, where you choose the type of enterprise bean that you are creating, select the bean type to be Message-Driven—most of the screen will blank
out at this point.
5. Select MDBPrintMessage from the drop-down list for Enterprise Bean Class. The
Enterprise Bean Name will be filled in automatically (see Figure 10.6).
FIGURE 10.6
Selecting the MessageDriven bean.
6. On the next Transaction Management screen, select Container-Managed (see
Figure 10.7).
13 0672323842 CH10
3/20/02
9:28 AM
Page 443
Message-Driven Beans
443
FIGURE 10.7
Selecting ContainerManaged transactions.
10
7. On the Message-Driven Bean Settings, select:
Destination type: queue
Destination: jms/firstQueue
Connection Factory: QueueConnectionFactory
Leave the JMS Message Selector blank (see Figure 10.8).
8. Select Finish.
9. Select the Verifier from the Tools menu. If the Verifier indicates that the bean has
failed a tests.ejb.SecurityIdentityRefs test, this can safely be ignored.
Security roles do not apply to a Message-driven bean because it runs within the
identity of the container.
10. Select Deploy from the Tools menu to bring up the screen shown in Figure 10.9.
11. You will not need the Client Jar, so deselect it.
12. Select Finish and check that the bean has been successfully deployed.
Listing 10.2 shows the XML deployment descriptor that has been created for you.
Highlighted in bold are those items that are of interest to you as a Message-driven bean
writer.
13 0672323842 CH10
3/20/02
9:28 AM
444
Page 444
Day 10
FIGURE 10.8
Selecting JNDI references for Destination
and Connection
Factory.
FIGURE 10.9
Deploying the
MDBPrintMessage
bean.
LISTING 10.2
Deployment Descriptor for MDBPrintMessage
1: <?xml version=”1.0” encoding=”UTF-8”?>
2: <!DOCTYPE ejb-jar PUBLIC ‘-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans
3: ➥ 2.0//EN’ ‘http://java.sun.com/dtd/ejb-jar_2_0.dtd’>
4: <ejb-jar>
5:
<display-name>MDBPrintMessage</display-name>
13 0672323842 CH10
3/20/02
9:28 AM
Page 445
Message-Driven Beans
LISTING 10.2
Continued
6:
<enterprise-beans>
7:
<message-driven>
8:
<display-name>MDBPrintMessage</display-name>
9:
<ejb-name>MDBPrintMessage</ejb-name>
10:
<ejb-class>MDBPrintMessage</ejb-class>
11:
<transaction-type>Container</transaction-type>
12:
<message-driven-destination>
13:
<destination-type>javax.jms.Queue</destination-type>
14:
</message-driven-destination>
15:
</message-driven>
16:
</enterprise-beans>
17:
<assembly-descriptor>
18:
<container-transaction>
19:
<method>
20:
<ejb-name>MDBPrintMessage</ejb-name>
21:
<method-intf>Bean</method-intf>
22:
<method-name>onMessage</method-name>
23:
<method-params>
24:
<method-param>javax.jms.Message</method-param>
25:
</method-params>
26:
</method>
27:
<trans-attribute>Required</trans-attribute>
28:
</container-transaction>
29:
</assembly-descriptor>
30: </ejb-jar>
Create a Sender Client to Create a Message
So far, you have created a Message-driven bean that is (as far as you are concerned)
waiting to handle any message sent to the jms/firstQueue queue. All that is left to do is
send a message to that queue and check that your bean is working correctly.
You can use the code or the PTPSender program described in Day 9 to send the message.
This is not an EJB, it is a simple client application, so it does not need to be deployed.
This code has been reproduced in Listing 10.3. for completeness.
LISTING 10.3
445
Point-to-Point Sender Code to Create and Send a Message to the
jms/firstQueue Queue
1: import javax.naming.*;
2: import javax.jms.*;
3:
4: public class PTPSender {
5:
6:
private QueueConnection queueConnection;
7:
private QueueSession queueSession;
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 446
446
Day 10
LISTING 10.3
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:
Continued
private QueueSender queueSender;
private Queue queue;
private static final String jndiFactory = “QueueConnectionFactory”;
private static final String jndiQueue = “jms/firstQueue”;
public static void main(String[] args) {
try {
PTPSender sender = new PTPSender(jndiFactory, jndiQueue);
System.out.println (“Sending message to jms/firstQueue”);
sender.sendMessage(“Here is a message sent to jms/firstQueue”);
sender.close();
} catch(Exception ex) {
System.err.println(“Exception in PTPSender: “ + ex);
}
}
public PTPSender(String jndiFactory, String jndiQueue)
➥ throws JMSException, NamingException {
Context context = new InitialContext();
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(jndiFactory);
queueConnection = queueFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
queue = (Queue)context.lookup(jndiQueue);
queueSender = queueSession.createSender(queue);
}
public void sendMessage(String msg) throws JMSException {
TextMessage message = queueSession.createTextMessage();
message.setText(msg);
queueSender.send(message);
}
public void close() throws JMSException {
//Send a non-text control message indicating end of messages
queueSender.send(queueSession.createMessage());
queueSender.close();
queueSession.close();
queueConnection.close();
}
}
Run the PTPSender program from the command line to put a message in the queue
jms/firstQueue.
13 0672323842 CH10
3/20/02
9:28 AM
Page 447
Message-Driven Beans
447
Check that you see the message:
“Here is a message sent to jms/firstQueue”
Now check that your message bean has received the message. If you started the J2EE RI
with the -verbose switch, you will see the output of the Message-driven bean in the
server window. If not, the output will be in the server log file.
“Received: Here is a message sent to jms/firstQueue “
Developing the Agency Case Study Example
Now you will turn your attention to a more realistic example. You will extend the Agency
case study to utilize a Message-driven bean to match advertised jobs to new applicants as
they register with the system or when an applicant updates his or her skills or location.
The steps are as follows:
1. Write a helper class that creates and sends a message to the jms/applicantQueue
containing the applicant’s login.
2. Amend the Agency and Register Session beans to call this new method when a
new applicant is registered or the applicant’s location or skills are changed.
3. Write a Message-driven bean to
• Consume a message on the jms/applicantQueue
• Look up the applicant’s location and skills information
• Find all the jobs that match the applicant’s location
• For each of these jobs, find those that require the applicant’s skills
• Determine if the applicant has all or just some of the skills
• Store applicant and job matches in the Matched table
4. Create the jms/applicantQueue queue.
5. Deploy the new EJBS; run and test the application.
Step 1—Sender Helper Class
This class contains a constructor for the class and two methods—sendApplicant() and
close().
The constructor takes two parameters, which are strings representing the JNDI names of
the JMS connection factory and the JMS queue.
public MessageSender(String jndiFactory, String jndiQueue)
➥ throws JMSException, NamingException {
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 448
448
Day 10
Context context = new InitialContext();
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(jndiFactory);
queueConnection = queueFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
queue = (Queue)context.lookup(jndiQueue);
queueSender = queueSession.createSender(queue);
}
The sendApplicant() method is called by the Agency Session bean when a new applicant registers with the system and the Register Session bean when an existing applicant
changes his or her location or job skills. It has two parameters—the applicant’s login
string and a Boolean denoting if this is a new applicant.
The close() method is called before the application is terminated. It sends a message
that lets the container know that no more messages will be sent to the queue and frees-up
resources.
The code for the MessageSender in shown in Listing 10.4, it should be very familiar by
now.
LISTING 10.4
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
MessageSender Helper Class
import javax.naming.*;
import javax.jms.*;
public class MessageSender {
private
private
private
private
QueueConnection queueConnection;
QueueSession queueSession;
QueueSender queueSender;
Queue queue;
public MessageSender(String jndiFactory, String jndiQueue)
➥ throws JMSException, NamingException {
Context context = new InitialContext();
QueueConnectionFactory queueFactory =
➥ (QueueConnectionFactory)context.lookup(jndiFactory);
queueConnection = queueFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false,
➥ Session.AUTO_ACKNOWLEDGE);
queue = (Queue)context.lookup(jndiQueue);
queueSender = queueSession.createSender(queue);
}
public void sendApplicant (String applicant, boolean newApplicant)
➥ throws JMSException {
13 0672323842 CH10
3/20/02
9:28 AM
Page 449
Message-Driven Beans
LISTING 10.4
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39: }
449
Continued
TextMessage message = queueSession.createTextMessage();
message.setBooleanProperty (“NewApplicant”, newApplicant);
message.setText(applicant);
queueSender.send(message);
}
public void close() throws JMSException {
//Send a non-text control message indicating end of messages
queueSender.send(queueSession.createMessage());
queueSender.close();
queueSession.close();
queueConnection.close();
}
10
Step 2—Agency and Register Session Bean
The following changes are required to AgencyBean.java and RegisterBean.java to call
the MessageSender.send() method when a new applicant is registered or the applicant’s
location or skills are changed.
1. In both AgencyBean.java and RegisterBean.java create a MessageSender object
in the setSessionContext() method..
private MessageSender messageSender;
public void setSessionContext(SessionContext ctx) {
/* existing code */
messageSender = new MessageSender (
➥ “java:comp/env/jms/QueueConnectionFactory”,
➥ “java:comp/env/jms/applicantQueue”);
}
2. In the AgencyBean.java file, add code to send a message indicating that a new
applicant has registered in the createApplicant() method. The added line is
shown in bold in the following code.
public void createApplicant(String login, String name,
➥ String email) throws DuplicateException, CreateException{
try {
ApplicantLocal applicant =
➥ applicantHome.create(login,name,email);
messageSender.sendApplicant(applicant.getLogin(),true);
}
catch (CreateException e) {
error(“Error adding applicant “+login,e);
}
13 0672323842 CH10
3/20/02
9:28 AM
450
Page 450
Day 10
catch (JMSException e) {
error(“Error sending applicant details to message
➥ bean “+login,e);
}
3. In the RegisterBean.java file, change updateDetails() to send a message to
indicate that the applicant’s details have changed. The added lines are shown in
bold in the following code.
public void updateDetails (String name, String email,
➥ String locationName, String summary, String[] skillNames) {
List skillList;
try {
skillList = skillHome.lookup(Arrays.asList(skillNames));
} catch(FinderException ex) {
error(“Invalid skill”, ex); // throws an exception
return;
}
LocationLocal location = null;
if (locationName != null) {
try {
location =
➥ locationHome.findByPrimaryKey(locationName);
} catch(FinderException ex) {
error(“Invalid location”, ex);
return;
}
}
applicant.setName(name);
applicant.setEmail(email);
applicant.setLocation(location);
applicant.setSummary(summary);
applicant.setSkills( skillList );
try {
messageSender.sendApplicant(applicant.getLogin(),false);
}
catch (JMSException ex) {
ctx.setRollbackOnly();
error (“Error sending applicant match message”,ex);
}
}
4. In both AgencyBean.java and RegisterBean.java, add the following to
ejbRemove() to close down the MessageSender.
try {
messageSender.close();
}
catch (JMSException ex) {
13 0672323842 CH10
3/20/02
9:28 AM
Page 451
Message-Driven Beans
451
error(“Error closing down the queue”,ex);
}
5. Compile and deploy this code.
Step 3—The Message-Driven Bean
Although this Message-driven bean is significantly larger than your previous example, it
does essentially the same thing.
This time, you need to obtain the JNDI InitialContext and use it to obtain references
to various Entity beans used in the code.
public void setMessageDrivenContext(MessageDrivenContext ctx) {
InitialContext ic = null;
try {
ic = new InitialContext();
applicantHome = (ApplicantLocalHome)ic.lookup(
➥ “java:comp/env/ejb/ApplicantLocal”);
}
catch (NamingException ex) {
error(“Error connecting to java:comp/env/ejb/ApplicantLocal:”,ex);
}
try {
jobHome = (JobLocalHome)ic.lookup(“java:comp/env/ejb/JobLocal”);
}
catch (NamingException ex) {
error(“Error connecting to java:comp/env/ejb/JobLocal:”,ex);
}
try {
matchedHome = (MatchedLocalHome)ic.lookup(
➥ “java:comp/env/ejb/MatchedLocal”);
}
catch (NamingException ex) {
error(“Error connecting to java:comp/env/ejb/MatchedLocal:”,ex);
}
}
The ejbCreate() method is blank.
public void ejbCreate(){}
The ejbRemove() cleans up by setting all the references to the Entity beans to null.
There are no other resources for you to deallocate.
public void ejbRemove(){
applicantHome = null;
jobHome = null;
matchedHome = null;
}
10
13 0672323842 CH10
452
3/20/02
9:28 AM
Page 452
Day 10
The onMessage() method is where you will code the algorithm that matches an applicant
to advertised jobs. First, you check that onMessage() has received the expected text message. The message contains the applicant’s login, which is the primary key on the
Applicants table. This primary key is used to obtain the applicant’s location and skills in
subsequent finder methods.
String login = null;
try {
if (!(message instanceof TextMessage)) {
System.out.println(“ApplicantMatch: bad message:” + message.getClass());
return;
}
Now, check if this applicant is a new one or if he or she has amended his or her registration. If the applicant has changed his or her details, you need to delete the existing
matches stored in the Matched table.
try {
login = ((TextMessage)message).getText();
if (! message.getBooleanProperty(“NewApplicant”)) {
matchedHome.deleteByApplicant(login);
}
}
catch (JMSException ex) {
error (“Error getting JMS property: NewApplicant”,ex);
}
Use the login primary key to find the applicant’s location using the Applicant Entity
bean’s finder method.
try {
ApplicantLocal applicant = applicantHome.findByPrimaryKey(login);
String location = applicant.getLocation().getName();
Next, obtain all the skills that the applicant has registered and store them in an array.
Collection skills = applicant.getSkills();
Collection appSkills = new ArrayList();
Iterator appIt = skills.iterator();
while (appIt.hasNext()) {
SkillLocal as = (SkillLocal)appIt.next();
appSkills.add(as.getName());
}
Now you have all the information you need about the applicant. The next step is to start
matching the jobs. First find the jobs that match the applicant’s location from the Job
bean.
Collection col = jobHome.findByLocation(location);
13 0672323842 CH10
3/20/02
9:28 AM
Page 453
Message-Driven Beans
453
Iterate over this collection finding the skills required for each job.
Iterator jobsIter = col.iterator();
while (jobsIter.hasNext()) {
JobLocal job = (JobLocal)jobsIter.next();
Collection jobSkills = job.getSkills();
Now you have a appSkills array containing the applicant’s skills and a jobSkills collection containing the skills required for the job. The next task is to find how many of
these skills match. This is done by iterating over the jobSkills, and for each jobSkill,
searching the appSkills array for a match. When a match is found, the skillMatch
counter is incremented.
int skillMatch = 0;
Iterator jobSkillIter = jobSkills.iterator();
while (jobSkillIter.hasNext()) {
SkillLocal jobSkill = (SkillLocal)jobSkillIter.next();
if (appSkills.contains(jobSkill.getName()))
skillMatch++;
}
Now see if you have a match. If there was a job skill to match (jobSkills.size() >0)
and the applicant did not have any of them (skillMatch == 0), get the next job (continue).
if (jobSkills.size() > 0 && skillMatch == 0)
continue;
Otherwise, determine if the applicant has all or just some of the skills.
boolean exact = skillMatch == jobSkills.size();
You are now in a position to update the Matched table. First create the primary key for
this table.
MatchedPK key = new MatchedPK(login,job.getRef(),job.getCustomer());
Now all that is left to do is store the applicant and job details in the Matched table.
try {
matchedHome.create(key.getApplicant(), key.getJob(),
➥ key.getCustomer(), exact);
}
catch (CreateException ex) {System.out.println(
➥ “ApplicantMatch: failed to create matched entry: “+key);
}
That is all there is to the bean apart from the exception handling. The full listing of the
ApplicantMatch Message-driven bean is shown in Listing 10.5.
10
13 0672323842 CH10
3/20/02
454
9:28 AM
Page 454
Day 10
LISTING 10.5
Full Listing on ApplicantMatch.java Message-Driven Bean Code
1: package data;
2:
3: import java.util.*;
4: import javax.ejb.*;
5: import javax.jms.*;
6: import javax.naming.*;
7:
8: public class ApplicantMatch implements MessageDrivenBean, MessageListener
9: {
10:
private ApplicantLocalHome applicantHome;
11:
private JobLocalHome jobHome;
12:
private MatchedLocalHome matchedHome;
13:
14:
public void onMessage(Message message) {
15:
String login = null;
16:
if (!(message instanceof TextMessage)) {
17:
System.out.println(“ApplicantMatch: bad message:” +
18: ➥ message.getClass());
19:
return;
20:
}
21:
try {
22:
login = ((TextMessage)message).getText();
23:
if (! message.getBooleanProperty(“NewApplicant”)) {
24:
matchedHome.deleteByApplicant(login);
25:
}
26:
}
27:
catch (JMSException ex) {
28:
error (“Error getting JMS property: NewApplicant”,ex);
29:
}
30:
try {
31:
ApplicantLocal applicant =
➥applicantHome.findByPrimaryKey(login);
32:
String location = applicant.getLocation().getName();
33:
Collection skills = applicant.getSkills();
34:
Collection appSkills = new ArrayList();
35:
Iterator appIt = skills.iterator();
36:
while (appIt.hasNext()) {
37:
SkillLocal as = (SkillLocal)appIt.next();
38:
appSkills.add(as.getName());
39:
}
40:
Collection col = jobHome.findByLocation(location);
41:
Iterator jobsIter = col.iterator();
42:
while (jobsIter.hasNext()) {
43:
JobLocal job = (JobLocal)jobsIter.next();
44:
Collection jobSkills = job.getSkills();
45:
int skillMatch = 0;
46:
Iterator jobSkillIter = jobSkills.iterator();
47:
while (jobSkillIter.hasNext()) {
13 0672323842 CH10
3/20/02
9:28 AM
Page 455
Message-Driven Beans
LISTING 10.5
455
Continued
48:
SkillLocal jobSkill = (SkillLocal)jobSkillIter.next();
49:
if (appSkills.contains(jobSkill.getName()))
50:
skillMatch++;
51:
}
52:
if (jobSkills.size() > 0 && skillMatch == 0)
53:
continue;
54:
boolean exact = skillMatch == jobSkills.size();
55:
MatchedPK key = new MatchedPK(login,job.getRef(),
56: ➥ job.getCustomer());
57:
try {
58:
matchedHome.create(key.getApplicant(),key.getJob(),
59: ➥ key.getCustomer(), exact);
60:
}
61:
catch (CreateException ex) {
62:
System.out.println(“ApplicantMatch: failed to create
63: ➥ matched entry: “+key);
64:
}
65:
}
66:
}
67:
catch (FinderException ex) {
68:
System.out.println(“ApplicantMatch: failed to find applicant
69: ➥ data: “+login);
70:
}
71:
catch (RuntimeException ex) {
72:
System.out.println(“ApplicantMatch: “+ex);
73:
ex.printStackTrace();
74:
throw ex;
75:
}
76:
}
77:
78:
// EJB methods start here
79:
80:
public void setMessageDrivenContext(MessageDrivenContext ctx) {
81:
InitialContext ic = null;
82:
try {
83:
ic = new InitialContext();
84:
applicantHome = (ApplicantLocalHome)ic.lookup(
85: ➥ “java:comp/env/ejb/ApplicantLocal”);
86:
}
87:
catch (NamingException ex) {
88:
error(“Error connecting to
➥java:comp/env/ejb/ApplicantLocal:”,ex);
89:
}
90:
try {
91:
jobHome =
➥(JobLocalHome)ic.lookup(“java:comp/env/ejb/JobLocal”);
92:
}
93:
catch (NamingException ex) {
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 456
456
Day 10
LISTING 10.5
Continued
94:
error(“Error connecting to java:comp/env/ejb/JobLocal:”,ex);
95:
}
96:
try {
97:
matchedHome = (MatchedLocalHome)ic.lookup(
98: ➥ “java:comp/env/ejb/MatchedLocal”);
99:
}
100:
catch (NamingException ex) {
101:
error(“Error connecting to
➥java:comp/env/ejb/MatchedLocal:”,ex);
102:
}
103:
}
104:
105:
public void ejbCreate(){
106:
}
107:
108:
public void ejbRemove(){
109:
applicantHome = null;
110:
jobHome = null;
111:
matchedHome = null;
112:
}
113:
114:
private void error (String msg, Exception ex) {
115:
String s = “ApplicantMatch: “+msg + “\n” + ex;
116:
System.out.println(s);
117:
throw new EJBException(s,ex);
118:
}
Compile this bean.
Step 4—Create the JMS Queue
Run the J2EE RI and use j2eeadmin to create the JMS queue.
j2eeadmin –addJMSDestination jms/applicantQueue queue
Alternatively, use deploytool and select Destinations from the Configure Installation
screen and add the queue.
Step 5—Deploy the EJBS
Now deploy the ApplicantMatch Message-driven bean. You will need to add the references to the following entity beans:
• applicant—Coded name java:comp/env/ejb/ApplicantLocal
• applicantSkill—Coded name java:comp/env/ejb/ApplicantSkillLocal
• job—Coded name java:comp/env/ejb/JobLocal
13 0672323842 CH10
3/20/02
9:28 AM
Page 457
Message-Driven Beans
457
• jobSkill—Coded name java:comp/env/ejb/JobSkillLocal
• matched—Coded name java:comp/env/ejb/MatchedLocal
Step 6—Testing the ApplicantMatch Bean
Run the Agency application using the appropriate runAll batch file for your operating
system. Use the Register screen to add a new applicant, whose location is London and
skills are Cigar Maker.
Use the Tables screen to view the contents of the Matched table and check that a row
has been added for the new applicant with the following details:
• Job—Cigar trimmer
• Customer—winston
• Exact—false
Add another applicant whose location is also London but and whose skills are Cigar
Maker and Critic. Check that this creates a row with the following details in the Matched
table:
• Job—Cigar trimmer
• Customer—winston
• Exact—true
Change the skills for this second applicant. Remove the Cigar Maker and Critic and add
the skill Bodyguard.
Check that the row for this applicant has now been deleted from the Matched table.
If these checks are okay, congratulations! You have successfully deployed the
ApplicantMatch Message-driven bean. Of course, you can add or amend other applicants to find other job matches in the system.
Using Other Architectures
Message-driven beans were designed to operate within the context of JMS implementations with messages sent by a JMS Server. This does not mean Message-driven beans can
not process messages sent by e-mail, HTTP, FTP, or any other protocol. As long as the
server is able to convert these protocols into a JMS message (simple encapsulation will
normally do), it can be handled by a Message-driven bean.
If the messages are defined in an open, extensible language like XML, unparalleled interoperability can be achieved in loosely-coupled systems using a model that is easy to
10
13 0672323842 CH10
3/20/02
9:28 AM
458
Page 458
Day 10
understand. This means that Message-driven beans have the potential to become the defacto model for handling any message type.
Summary
Today, you have created some simple Message-driven beans and seen how easy it is consume JMS messages. Message-driven beans are a useful addition to the existing entity
and session EJBs, offering a way for you to write asynchronous message consumers. You
have seen how the container manages the life cycle of the bean, its transactions and security, so that having deployed your Message-driven beans, you can forget about them.
Q&A
Q What are the major differences between Message-driven beans and Entity or
Session beans?
A Message-driven beans have no client interface; they have no Home, Local, or
Remote interfaces—their methods cannot be called directly. Message-driven beans
exist only to consume JMS messages and are controlled by the container. They are
anonymous and are called asynchronously. They do not have a passive state. They
have no client security context.
Q What are the two interfaces a Message-driven bean must implement?
A The javax.ejb.MessageDrivenBean interface and the
javax.jms.MessageListener interface.
Q What is the Method Ready Pool?
A The Method Ready Pool is the collection of Message-driven bean instances that are
available in the container to consume JMS messages.
Q How can I associate a Message-driven bean with a queue or a topic?
A A queue or topic is associated with a Message-driven bean at deploy time. The
queue or topic must have already been registered with the J2EE system.
Exercise
To extend your knowledge of the Message-driven beans, try the following exercise.
1. Extend the Agency case study. Add a Message-driven bean that receives a message
from the AdvertiseJob Session bean when a new job is advertised. The Messagedriven bean should search through all the applicants to find those suitable to be
considered for the job. To be considered for a job, the applicant must match the
job’s:
13 0672323842 CH10
3/20/02
9:28 AM
Page 459
Message-Driven Beans
459
• Location
• At least one skill
If the applicant has all the required skills set exactMatch to true; otherwise,
false.
All applicants that match at least one skill must be added to the Matched table.
Don’t forget to create a JMS queue for the messages (you can’t use the same queue
as the one used in the Applicant example).
Add some new jobs and use the TableClient program to check that rows are being
added to the Matched table (this will only happen if are some applicants that match
the job’s location and skills).
2. Extend your previous solution to update the Matched table when job adverts are
changed. Hint: you can delete the old matched rows and add the applicants that
match the new criteria rather than try to update the rows.
For completeness you should update the Register and Agency beans so that when
a job or customer is removed, all their entries in the Matched table are also
removed. The Matched Entity bean has suitable home methods that support this
functionality.
10
13 0672323842 CH10
3/20/02
9:28 AM
Page 460
14 0672323842 CH11
3/20/02
9:39 AM
Page 461
WEEK 2
DAY
11
JavaMail
In Day 9, “Java Message Service,” and Day 10, “Message-Driven Beans,” you
learned about the Java Messaging Service and Message-driven beans. These
technologies allow you to write code that provides application-to-application
communication. Today’s lesson looks at how you can provide application-tohuman communication through the use of e-mail.
Today you learn how e-mail systems work and how the JavaMail API models
these systems. You will then explore the API’s main classes and see how to use
these to provide typical day-to-day e-mail functionality, such as sending attachments and deleting messages. Finally, you will have the opportunity to expand
on the case study you have been developing so that it sends e-mail messages to
people who register with the service.
Today’s lesson covers the following topics:
• Understanding e-mail systems, formats, and protocols
• Creating and sending plain text e-mail messages
• Creating and sending multi-media e-mail messages
• Sending e-mail messages with attachments
14 0672323842 CH11
3/20/02
9:39 AM
Page 462
462
Day 11
• Retrieving e-mail messages and attachments
• Deleting e-mail messages on the server
• Authenticating users and security
Understanding E-Mail
E-mail is something that most people take for granted without ever really understanding
how it works. If you want to write applications that send and receive e-mail messages, it
is essential to have some understanding of a typical e-mail system environment.
E-mail messages are sent on a client/server basis, but one that is different to that used for
Web pages. Figure 11.1 shows a typical e-mail delivery process. As you can see, both the
sender and recipient act as clients to e-mail servers. The sender creates a message and
this forwards to an e-mail server. The server then uses the Simple Mail Transfer Protocol
(SMTP) to send the message across the Internet to the recipient’s mail box on another email server. The receiver then uses a retrieval protocol, such as Post Office Protocol
(POP3) or Internet Message Access Protocol (IMAP), to retrieve the message from their
e-mail server.
FIGURE 11.1
Sender’s Mail
Server
A simple e-mail architecture.
SMTP
Recipient’s Mail
Server
Mailbox
Retrieval
Protocol
Message Sender
Mailbox
Message
Recipient
Mailbox
The actual e-mail message itself consists of two sections—the header and the body. Mail
headers are name-value pairs that define certain attributes of a particular message, such
as who the sent the message and when the message was sent. The body is the actual email message. Originally, the message body could only contain ASCII-based text. The
standard 128-character ASCII set and the inability to embed multimedia objects or attach
files meant that e-mail was restrictive. Over the years, there have been a number of ways
to expand the functionality of e-mail. Today, you can use the Multipurpose Internet Mail
Extensions (MIME) format to construct and send content-rich e-mail messages that are
14 0672323842 CH11
3/20/02
9:39 AM
Page 463
JavaMail
463
not limited by the standard 128-character ASCII set. You will learn more about MIME in
just a moment, but first, today’s lesson will provide you with an overview of some the
commonly used e-mail protocols.
SMTP
Simple Mail Transfer Protocol (SMTP) is the protocol that mail servers use to send messages to one another. SMTP communication occurs over TCP port 25 using a simple
sequence of four-character client commands and three-digit server responses. It is unlikely that you will ever have to communicate using SMTP directly with a mail server, but it
might interest you to see a typical conversation between a client and a mail server:
HELO madeupdomain.com
250 Hello host127-0-0-1.anotherdomain.com [127.0.0.1]
MAIL FROM: me@anotherdomain.com
250 < me@anotherdomain.com > is syntactically correct
RCPT TO: user@madeupdomain.com
250 < user@madeupdomain.com > is syntactically correct
DATA
354 Enter message, ending with “.” on a line by itself
Hello World!
.
250 OK id=1643UJ-00030Z-00
QUIT
221 closing connection
You can see just how simple the protocol is. The client connects to the server and issues
a HELO command and the server responds with a 250 (OK) response. The client then
issues a MAIL FROM: command and a RCPT TO: command and, in both instances, the
server replies with a 250 response. The client then issues a DATA command and sends a
message. Finally, the server issues a 250 response and the client issues the QUIT command.
The important thing to note about SMTP is that it is not used to deliver a message directly to the recipient but, instead, delivers it the recipient’s mail server. This mail server
then forwards the message to the recipient’s mail box—a file or other repository that is
held on the server—and not the recipient’s client machine.
Post Office Protocol 3 (POP3)
POP3 is a protocol that allows message recipients to retrieve e-mail messages stored in a
mailbox on a mail server. The protocol operates on TCP port 110 and, like SMTP, uses a
series of simple requests and responses. Unlike SMTP, users must provide authentication
credentials (username and password) before they can download e-mail messages from
their mail boxes.
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 464
464
Day 11
Many e-mail clients use POP3, although the protocol allows quite limited server-side
manipulation of messages. On the server, the user may list, delete, and retrieve e-mail
messages from his or her mail box.
Internet Message Access Protocol (IMAP)
Like POP3, IMAP is an e-mail message retrieval protocol. Also like POP3, it works on a
simple request-response model, but it does operate on a different TCP port—port 143.
The biggest difference between the two protocols is that IMAP transfers a lot of clientside functionality to the server. For example, you can browse messages’ subjects and
sizes and senders’ addresses before you decide to download the messages to your local
machine. You can also create, delete, and manipulate folders on the server, and move
messages between these folders.
Other Protocols
The three previously mentioned protocols are prevalent current e-mail systems, but there
are other e-mail protocols. Some of these are previous versions of existing protocols. For
example, some machines might still run POP2 servers. Other protocols are variations on
those previously mentioned—for example, IMAP over SSL. Finally, there are a variety of
protocols that provide either specialist functionality or different interpretations on the
popular protocols. For example, the Network News Transport Protocol (NNTP) is the
main protocol clients and servers use with Usenet newsgroups. If you want to learn more
about this protocol, refer to Request for Comments (RFC) 997, which is available at
http://www.rfc.net/rfc997.html.
Multipurpose Internet Mail Extensions (MIME)
The MIME format extends the capabilities of e-mail beyond the 128-character ASCII set
to provide
• Message bodies in character sets other than US-ASCII
• Extensible formats for non-textual message bodies, such as images
• Multipart message bodies (you’ll learn about these later today)
• Header information in character sets other than US-ASCII
• Unlimited message body length
The MIME standard provides a standard way of encoding many different types of data,
such as GIF images and MPEG videos. MIME defines additional message headers that a
client can then use to unpack, decode, and interpret the data the message body contains.
The message body may consist of several body parts including attachments. The
14 0672323842 CH11
3/20/02
9:40 AM
Page 465
JavaMail
465
“Creating Multi-Media E-mails” section of today’s lesson explores MIME in more depth.
You can also find out more about MIME by reading RFCs 2045 through to 2049, which
you can access at http://www.rfc.net/.
Introducing the JavaMail API
As you have seen, e-mail systems have relatively complex architectures and use a selection of transport protocols. In the past, a developer wanting to send or retrieve e-mail
messages would have to use TCP sockets and, using an appropriate protocol, talk directly
to an e-mail server. As you can imagine, coding such applications was typically involved
and intensive. Alternatively, a developer would have to use a vendor-specific API to
access e-mail functionality, locking their code into one platform or technology. The
JavaMail API changes all of this.
The API provides a generic model of an e-mail system, which allows you to send and
retrieve messages in a platform independent and protocol independent way. In addition,
JavaMail allows you to simply create different types of messages, such as plain text,
those with attachments, or those with mixed binary content. Sun Microsystems’ reference
implementation of JavaMail supports the three most popular e-mail protocols—SMTP,
POP3, and IMAP. Other protocols are available separately, for example, there are thirdparty implementations that support IMAP over SSL and NNTP.
Setting up Your Development Environment
If you downloaded and installed the J2EE reference implementation, you are ready to
start exploring the JavaMail API. If you did not install the reference implementation, you
will need to install the JavaMail API and Java Activation Framework (JAF) before you
can start writing code.
Before you download the JavaMail API, ensure that you have J2SE 1.2.X or later correctly installed. You must also install the Java Activation Framework (JAF) because the
JavaMail API requires it. The API requires the framework to handle arbitrary large
blocks of data (you will learn more about this later in today’s lesson). You can download
JAF from http://java.sun.com/products/javabeans/glasgow/jaf.html.
Note
Both JAF and the JavaMail API are written in pure Java, so they will operate
on any platform running JDK 1.1 or later.
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 466
466
Day 11
After you have downloaded the framework, installation is straightforward. Simply unzip
the download file and append the location of activation.jar to your class path. To do
this on a Windows platform, issue the following command:
set CLASSPATH=%CLASSPATH%;C:\jaf-VERSION\activation.jar
On a Unix-based system that has a Bourne or Korn shell, use the following:
$CLASSPATH=$CLASSPATH:usr/java/jaf-VERSION
$export $CLASSPATH
You have now installed JAF; now download the JavaMail API from
http://java.sun.com/products/javamail/index.html. When downloaded, the actual
installation of JavaMail is as simple as installing JAF. Simply unzip the download file
and append the location of mail.jar to your class path.
That’s it! You have successfully installed the JavaMail API. You will find a selection of
demonstration applications in the demo directory under the JavaMail installation directory. In addition, you can find the API documentation in the javadocs directory, which is
also under the installation directory.
Sending a First E-mail
This section introduces you to the core classes you need to send an e-mail message.
Specifically, you will use these classes to write code that accepts a SMTP host and mail
recipient from the command line, and then sends a plain text e-mail to that recipient.
Although the application uses the command line, you can modify the code for use in
other situations if you want. For example, you could modify the code so that it accepts
parameters from another application or presents the user with a desktop or applet GUI
written using Swing or AWT.
Creating a First E-mail
Now that you have set up your development environment, you can write the code that
will send an e-mail message. This application uses classes from the javax.mail package
and the javax.mail.internet package. The javax.mail package provides classes that
model a mail system, for example all mail systems require messages to contain a message body. The javax.mail.internet package provides classes that are specific to
Internet mail systems, for example, a class that allows you to manipulate the content type
of a message. This application commences by importing both of these packages and
java.util.Properties, which allows you to set the SMTP host as a system property.
14 0672323842 CH11
3/20/02
9:40 AM
Page 467
JavaMail
467
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
Because this is a simple application, the main() method contains all the code to collect
user input and send the e-mail message. In this example, the user will pass three parameters to the application via the command line—SMTP host, recipient’s e-mail address, and
sender’s e-mail address:
public class SendMail {
public static void main(String[] args) {
if (args.length!=3) {
System.out.println
➥(“Usage: SendMail SMTPHost ToAddress FromAddress”);
System.exit(1);
}
String smtpHost=args[0];
String to=args[1];
String from = args[2];
Before you can create and send an e-mail message, you must have authenticated access
to a SMTP host. If you are unsure of your SMTP host, either check with your system
administrator or obtain the host address from the configuration of an e-mail application.
To help you identify the host, the naming conventions for hosts means that your SMTP
host is likely to take the form of smtp.host or mail.host. After you identify the SMTP
host, you must make it available as a system property to the JVM. To do this, you must
add the host name to the system properties list:
Properties props = System.getProperties();
props.put(“mail.smtp.host”,smtpHost);
The first line of code gets a Properties object that represents the system properties. The
code then writes a key/value pair to the system properties list. The first argument of the
put() method, mail.smtp.host, is defined by Appendix A of the JavaMail specification.
Table 11.1 shows all of the properties the specification defines.
TABLE 11.1
Mail Environment Properties
Property
Description
mail.store.protocol
Specifies the default message access protocol. The default value is the
first appropriate protocol in the mail configuration files.
mail.transport.
Specifies the default mail transport protocol. The default value is the first
appropriate protocol in the mail configuration files.
protocol
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 468
468
Day 11
TABLE 11.1
Continued
Property
Description
mail.host
Specifies the default mail server. The default value is the local machine.
mail.user
Specifies the default username used when connecting to the mail server.
The default value is user.name.
mail.protocol.host
Specifies the protocol-specific mail server. This property overrides any
value you specify for mail.host. The default value is mail.host.
mail.protocol.user
Specifies the protocol-specific username used when connecting to the
mail server. This property overrides the value of mail.user. The default
value is mail.user.
mail.from
Specifies the return address of the current user. The default value is
username@host.
mail.debug
Specifies whether debug is enabled or disabled. The default value is
false—debug is disabled.
In this example, you overrode the value of mail.protcol.host, which in turn overrides
the value of mail.host.
Now that the code defines a Properties object containing the SMTP host, you create a
mail session. A Session object represents the mail session, and all e-mail functionality
works through this object. The Session object has two private constructors. The first provides a unique mail session that applications cannot share. To get this type of session you
call the getInstance() method:
Session session = Session.getInstance(props, null);
In this example, the first argument is the Properties object that you previously created.
The second argument, null, is an Authenticator object, which today’s lesson discusses
later in the “Authenticating Users and Security” section. The second constructor also
takes these arguments, but it differs in that it creates a Session object that applications
can share. It is this type of Session your first e-mail application uses:
Session session = Session.getDefaultInstance(props, null);
After you create a Session, you can start creating an e-mail message. The Message class
represents an e-mail message, but it is an abstract class, so you must implement a subclass. The JavaMail API defines only one subclass, MimeMessage, that represents a standard MIME style e-mail message. The class has five constructors, but you will only use
one of these at the moment—the default constructor. This accepts a single parameter—a
Session object:
MimeMessage message = new MimeMessage(session);
14 0672323842 CH11
3/20/02
9:40 AM
Page 469
JavaMail
469
This creates an empty MimeMessage object. You must now fill the object with appropriate
attributes and content—in this example, subject, message text, from address, and to
address. The MimeMessage class exposes a large number of methods for getting and settings these attributes. Today, you will explore several of these methods, but for a complete guide, refer to the API documentation. In this example, you first set the text of the
message by using the setText() method:
message.setText(“Hi from the J2EE in 21 Days authors”);
The setText() method not only sets the message’s text, but it also sets the content type
of the message to text/plain. If the content is not of the type text/plain, you must
first use the setContent() method to set the message’s content type—you will learn how
to do this later today in the “Authenticating Users and Security” section. The setText()
method throws an exception of the type MessagingException, which is the base class for
all messaging class exceptions. The majority of the methods associated with the mail
packages throw this exception or one of its subclasses, so inevitably you have to catch
these exceptions.
To set the subject of the message, you use the setSubject() method, which takes a
string representing the subject as a parameter:
message.setSubject(“Hi!”);
The message now requires the addresses of the recipient and sender. To set the sender’s
address, you use the setFrom() method. The method takes a single argument of the type
Address. The Address class represents an address in an e-mail message. The class is
abstract, so you must use one of its subclasses, namely InternetAddress. This class has
four constructors; the one you use takes a single parameter—an e-mail address as a
string:
message.setFrom(new InternetAddress(from));
You provide the recipient’s address to the MimeMessage object in a similar way. But you
must also stipulate to which type of recipient the address relates. The Message class has
one inner class, RecipientType, that defines the type of recipient within an e-mail message. The class exposes three fields that relate to the different recipient types:
• BCC—Blind carbon copy recipients
• CC—Carbon copy recipients
• TO—TO recipients
In this application, you only send the message to one recipient, so you use the TO field.
When you set the from address, you used the setFrom() method, and you use the
addRecipient() method to set the To address. The code to set the recipient’s address
appears as follows:
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 470
470
Day 11
message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));
You have now created the message, now you must send the message. The Transport
class, which models an e-mail transport system, provides you with the means to send the
message. The class provides two static send() methods that send messages. The first
form takes a Message object and an array of Address objects as arguments. It sends the
message to all the recipients the Address objects define and ignores any addresses the
message defines. The second version of this method takes one parameter—a Message
object. Note that the Message object must define at least one recipient’s address. It is this
method you will now use:
Transport.send(message);
The send() method may throw a MessagingException or a SendFailedException. The
SendFailedException is thrown if the send() method fails to send one or more messages. The exception traceback includes a list of all the e-mail addresses to which it
could not send messages.
You have now completed the code to send an e-mail. Listing 11.1 shows the complete
code:
LISTING 11.1
SendMail.java Full Listing
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;
public class SendMail {
public static void main(String[] args) {
if (args.length!=3) {
System.out.println(“Usage: SendMail SMTPHost ToAddress
➥FromAddress”);
System.exit(1);
}
String smtpHost = args[0];
String to = args[1];
String from = args[2];
// Get properties object
Properties props = System.getProperties();
// Define SMTP host property
props.put(“mail.smtp.host”,smtpHost);
try {
14 0672323842 CH11
3/20/02
9:40 AM
Page 471
JavaMail
LISTING 11.1
471
Continued
// Get a session
Session session = Session.getDefaultInstance(props,null);
// Create a new message object
MimeMessage message = new MimeMessage(session);
// Populate message object
message.setText(“Hi from the J2EE in 21 Days authors.”);
message.setSubject(“Hi!”);
message.setFrom(new InternetAddress(from));
message.addRecipient
➥(Message.RecipientType.TO,new InternetAddress(to));
System.out.println(“Message Sent”);
// Send the message
Transport.send(message);
}
catch (MessagingException me) {
System.err.println(me.getMessage());
}
}
}
To run this application, you must first compile it. Compile it from the command line
using javac as you would for any other Java application. After it compiles, run the application by issuing the following command:
java SendMail mail.yourSMTPHost.com toAddress fromAddress
When the program executes, a Message Sent message should display. If the program
fails to execute correctly, check the error messages the JVM displays. The most likely
cause of failure is an incorrect SMTP host or one that is inaccessible from your system.
There are several ways you can check whether the host exists and is visible. For example, you can run a trace route to your SMTP host; under Windows (all versions) issue the
following command at the command prompt:
tracert yourSMTPHost
On Unix or Linux systems, use the following:
traceroute yourSMTPHost
If the host does not exist or is inaccessible, you will receive a message indicating that
your trace route program could not resolve the hostname you specified, or on some systems, the displayed route will suddenly die.
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 472
472
Day 11
Creating Multi-Media E-mails
Most modern e-mail clients have the capability of displaying formatted HTML messages.
Many organizations now prefer to send HTML messages because they provide a richer
viewing experience for the recipient. For example, a HTML message might incorporate
an organization’s logo and format the message’s text so that it adheres to the organization’s style preferences (font face, heading colors).
In the following example, HTML is used as the multi-media content. However, there is
no intention to teach you HMTL (if you do not already know it), so the HTML is supplied for you.
The application you will now write is a typical example of an application an organization
might use to send a marketing or informational e-mail to customers or potential customers. The application sends a HTML message to the recipient that contains an image
file representing the organization’s logo. To view the formatted output of the message,
you must have an e-mail client that is capable of displaying HTML, such as Netscape
Communicator, Microsoft Outlook, or a Java application that uses Swing JEditorPane.
There are two main ways of creating an HTML message that will display an image when
viewed in an e-mail client. The simplest is when the HTML code references a URL that
points to a publicly visible image location. This approach is simple to code, but does
require that the image remains publicly visible for the potential lifetime of the message.
In addition, it requires the e-mail client either to always have an external connection or
cache a local copy of the images. The second approach is when you integrate the images
into the message. This approach ensures that the recipient always sees the images.
However, it does entail sending larger messages, which places higher loads on your server and the client’s mail server. Today’s lesson shows you both of these approaches starting with the external reference approach.
Creating the Message: Approach #1
The code for this approach to sending a HTML message is very similar to the code that
sends a plain text message. The start of the code has a few additions. First, the code
imports the java.io package to provide the classes that read the HTML file. Second, the
code declares two extra variables—one defines the content type of the message and the
other names the HTML source file.
import
import
import
import
java.util.Properties;
javax.mail.*;
javax.mail.internet.*;
java.io.*;
14 0672323842 CH11
3/20/02
9:40 AM
Page 473
JavaMail
473
public class SendHTMLMail {
public static void main(String[] args) {
if (args.length!=3) {
System.out.println
➥(“Usage: SendHTMLMail SMTPHost ToAddress FromAddress”);
System.exit(1);
}
String smtpHost = args[0];
String to = args[1];
String from = args[2];
String contentType = “text/html”;
String htmlFile = “HTMLSource1.html”;
In common with the plain text message, you create Properties, Session and Message
objects. You also set the subject and sender’s and recipient’s addresses.
Properties props = System.getProperties();
props.put(“mail.smtp.host”,smtpHost);
Session session = Session.getDefaultInstance(props,null);
MimeMessage message = new MimeMessage(session);
message.setSubject(“Hi!”);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));
Adding the HTML to the MimeMessage object is the first significant change to the code.
Because the HTML is stored in a file, you must first read the file’s contents. The following code does this; as you can see, it simply uses the java.io classes:
String msg=””;
String line=null;
FileReader fr = new FileReader(htmlFile);
BufferedReader br = new BufferedReader(fr);
while ((line=br.readLine())!=null) {
msg+=line;
}
The code that sent a plain text message used the setText() method to set the message
text. In this example, you use another method—setContent(). This method takes two
parameters—an object that represents the message body and a string that defines the
MIME type of the message body. This method allows you to stipulate the MIME type of
the message, whereas the setText() method always applies a MIME type of
text/plain. In the application you are currently building, the setContent() method
takes a first argument of a string (the HTML) and a second argument of a variable with
the value text/html:
message.setContent(msg,contentType);
After you define the message body, you send the message using the send() method of
the Transport class. This is exactly the same approach you took when sending a plain
11
14 0672323842 CH11
3/20/02
9:40 AM
Page 474
474
Day 11
text message, so that code fragment isn’t shown here. Instead, Listing 11.2 shows the
complete code for sending a HTML e-mail message.
LISTING 11.2
import
import
import
import
SendHTMLMail.java Full Listing
java.util.Properties;
javax.mail.*;
javax.mail.internet.*;
java.io.*;
public class SendHTMLMail {
public static void main(String[] args) {
if (args.length!=3) {
System.out.println
➥(“Usage: SendHTMLMail SMTPHost ToAddress FromAddress”);
System.exit(1);
}
String
String
String
String
String
smtpHost = args[0];
to = args[1];
from = args[2];
contentType = “text/html”;
htmlFile = “HTMLSource1.html”;
// Get properties object
Properties props = System.getProperties();
// Define SMTP host property
props.put(“mail.smtp.host”,smtpHost);
try {
// Get a session
Session session = Session.getDefaultInstance(props,null);
// Create a new message object
MimeMessage message = new MimeMessage(session);
// Populate message object
message.setSubject(“Hi!”);
message.setFrom(new InternetAddress(from));
message.addRecipient
➥(Message.RecipientType.TO,new InternetAddress(to));
// read the HTML source
String msg=””;
String line=null;
FileReader fr = new FileReader(htmlFile);
BufferedReader br = new BufferedReader(fr);
while ((line=br.readLine())!=null) {
14 0672323842