KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > vlib > ejb > impl > OperationsBean


1 // Copyright 2004 The Apache Software Foundation
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15 package org.apache.tapestry.vlib.ejb.impl;
16
17 import java.rmi.RemoteException JavaDoc;
18 import java.sql.Connection JavaDoc;
19 import java.sql.ResultSet JavaDoc;
20 import java.sql.SQLException JavaDoc;
21 import java.sql.Timestamp JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import javax.ejb.CreateException JavaDoc;
28 import javax.ejb.FinderException JavaDoc;
29 import javax.ejb.RemoveException JavaDoc;
30 import javax.ejb.SessionBean JavaDoc;
31 import javax.ejb.SessionContext JavaDoc;
32 import javax.naming.Context JavaDoc;
33 import javax.naming.InitialContext JavaDoc;
34 import javax.naming.NamingException JavaDoc;
35 import javax.rmi.PortableRemoteObject JavaDoc;
36 import javax.sql.DataSource JavaDoc;
37
38 import org.apache.tapestry.Tapestry;
39 import org.apache.tapestry.contrib.ejb.XCreateException;
40 import org.apache.tapestry.contrib.ejb.XEJBException;
41 import org.apache.tapestry.contrib.ejb.XRemoveException;
42 import org.apache.tapestry.contrib.jdbc.IStatement;
43 import org.apache.tapestry.contrib.jdbc.StatementAssembly;
44 import org.apache.tapestry.vlib.ejb.Book;
45 import org.apache.tapestry.vlib.ejb.BorrowException;
46 import org.apache.tapestry.vlib.ejb.IBook;
47 import org.apache.tapestry.vlib.ejb.IBookHome;
48 import org.apache.tapestry.vlib.ejb.IPerson;
49 import org.apache.tapestry.vlib.ejb.IPersonHome;
50 import org.apache.tapestry.vlib.ejb.IPublisher;
51 import org.apache.tapestry.vlib.ejb.IPublisherHome;
52 import org.apache.tapestry.vlib.ejb.LoginException;
53 import org.apache.tapestry.vlib.ejb.Person;
54 import org.apache.tapestry.vlib.ejb.Publisher;
55 import org.apache.tapestry.vlib.ejb.RegistrationException;
56 import org.apache.tapestry.vlib.ejb.SortColumn;
57 import org.apache.tapestry.vlib.ejb.SortOrdering;
58
59 /**
60  * Implementation of the {@link org.apache.tapestry.vlib.ejb.IOperations}
61  * stateless session bean.
62  *
63  * <p>Implenents a number of stateless operations for the front end.
64  *
65  * @version $Id: OperationsBean.java,v 1.7 2004/02/19 17:38:07 hlship Exp $
66  * @author Howard Lewis Ship
67  *
68  **/

69
70 public class OperationsBean implements SessionBean JavaDoc
71 {
72     private SessionContext JavaDoc _context;
73     private transient Context JavaDoc _environment;
74     private transient IBookHome _bookHome;
75     private transient IPersonHome _personHome;
76     private transient IPublisherHome _publisherHome;
77
78     /**
79      * Data source, retrieved from the ENC property
80      * "jdbc/dataSource".
81      *
82      **/

83
84     private transient DataSource JavaDoc _dataSource;
85
86     /**
87      * Sets up the bean. Locates the {@link DataSource} for the bean
88      * as <code>jdbc/dataSource</code> within the ENC; this data source is
89      * later used by {@link #getConnection()}.
90      *
91      **/

92
93     public void ejbCreate()
94     {
95         Context JavaDoc initial;
96
97         try
98         {
99             initial = new InitialContext JavaDoc();
100             _environment = (Context JavaDoc) initial.lookup("java:comp/env");
101         }
102         catch (NamingException JavaDoc e)
103         {
104             throw new XEJBException("Could not lookup environment.", e);
105         }
106
107         try
108         {
109             _dataSource = (DataSource JavaDoc) _environment.lookup("jdbc/dataSource");
110         }
111         catch (NamingException JavaDoc e)
112         {
113             e.printStackTrace();
114             throw new XEJBException("Could not lookup data source.", e);
115         }
116     }
117
118     public void ejbRemove()
119     {
120     }
121
122     /**
123      * Does nothing, not invoked in stateless session beans.
124      **/

125
126     public void ejbPassivate()
127     {
128     }
129
130     public void setSessionContext(SessionContext JavaDoc value)
131     {
132         _context = value;
133     }
134
135     /**
136      * Does nothing, not invoked in stateless session beans.
137      *
138      **/

139
140     public void ejbActivate()
141     {
142     }
143
144     /**
145      * Finds the book and borrower (by thier primary keys) and updates the book.
146      *
147      * <p>The {@link Book} value object is returned.
148      *
149      **/

150
151     public Book borrowBook(Integer JavaDoc bookId, Integer JavaDoc borrowerId)
152         throws FinderException JavaDoc, RemoteException JavaDoc, BorrowException
153     {
154         IBookHome bookHome = getBookHome();
155         IPersonHome personHome = getPersonHome();
156
157         IBook book = bookHome.findByPrimaryKey(bookId);
158
159         if (!book.getLendable())
160             throw new BorrowException("Book may not be borrowed.");
161
162         // Verify that the borrower exists.
163

164         personHome.findByPrimaryKey(borrowerId);
165
166         // TBD: Check that borrower has authenticated
167

168         // findByPrimaryKey() throws an exception if the EJB doesn't exist,
169
// so we're safe.
170

171         personHome.findByPrimaryKey(book.getOwnerId());
172
173         // Here's the real work; just setting the holder of the book
174
// to be the borrower.
175

176         book.setHolderId(borrowerId);
177
178         return getBook(bookId);
179     }
180
181     /**
182      * Adds a new book, verifying that the publisher and holder actually exist.
183      *
184      **/

185
186     public Integer JavaDoc addBook(Map JavaDoc attributes) throws CreateException JavaDoc, RemoteException JavaDoc
187     {
188         IBookHome home = getBookHome();
189
190         attributes.put("dateAdded", new Timestamp JavaDoc(System.currentTimeMillis()));
191
192         IBook book = home.create(attributes);
193
194         return (Integer JavaDoc) book.getPrimaryKey();
195     }
196
197     /**
198      * Adds a book, which will be owned and held by the specified owner.
199      *
200      * <p>The publisherName may either be the name of a known publisher, or
201      * a new name. A new {@link IPublisher} will be created as necessary.
202      *
203      * <p>Returns the newly created book, as a {@link Map} of attributes.
204      *
205      **/

206
207     public Integer JavaDoc addBook(Map JavaDoc attributes, String JavaDoc publisherName)
208         throws CreateException JavaDoc, RemoteException JavaDoc
209     {
210         IPublisher publisher = null;
211         IPublisherHome publisherHome = getPublisherHome();
212
213         // Find or create the publisher.
214

215         try
216         {
217             publisher = publisherHome.findByName(publisherName);
218         }
219         catch (FinderException JavaDoc e)
220         {
221             // Ignore, means that no publisher with the given name already exists.
222
}
223
224         if (publisher == null)
225             publisher = publisherHome.create(publisherName);
226
227         attributes.put("publisherId", publisher.getPrimaryKey());
228
229         return addBook(attributes);
230     }
231
232     /**
233      * Updates a book.
234      *
235      * <p>Returns the updated book.
236      *
237      * @param bookId The primary key of the book to update.
238      *
239      **/

240
241     public void updateBook(Integer JavaDoc bookId, Map JavaDoc attributes) throws FinderException JavaDoc, RemoteException JavaDoc
242     {
243         IBookHome bookHome = getBookHome();
244
245         IBook book = bookHome.findByPrimaryKey(bookId);
246
247         book.updateEntityAttributes(attributes);
248     }
249
250     /**
251      * Updates a book, adding a new Publisher at the same time.
252      *
253      *
254      * @param bookPK The primary key of the book to update.
255      * @param attributes attributes to change
256      * @param publisherName The name of the new publisher.
257      * @throws FinderException if the book, holder or publisher can not be located.
258      * @throws CreateException if the {@link IPublisher} can not be created.
259      **/

260
261     public void updateBook(Integer JavaDoc bookId, Map JavaDoc attributes, String JavaDoc publisherName)
262         throws CreateException JavaDoc, FinderException JavaDoc, RemoteException JavaDoc
263     {
264         IPublisher publisher = null;
265
266         IPublisherHome publisherHome = getPublisherHome();
267
268         try
269         {
270             publisher = publisherHome.findByName(publisherName);
271         }
272         catch (FinderException JavaDoc e)
273         {
274             // Ignore, means we need to create the Publisher
275
}
276
277         if (publisher == null)
278             publisher = publisherHome.create(publisherName);
279
280         // Don't duplicate all that other code!
281

282         attributes.put("publisherId", publisher.getPrimaryKey());
283
284         updateBook(bookId, attributes);
285     }
286
287     public void updatePerson(Integer JavaDoc personId, Map JavaDoc attributes)
288         throws FinderException JavaDoc, RemoteException JavaDoc
289     {
290         IPersonHome home = getPersonHome();
291
292         IPerson person = home.findByPrimaryKey(personId);
293
294         person.updateEntityAttributes(attributes);
295     }
296
297     public Publisher[] getPublishers()
298     {
299         Connection JavaDoc connection = null;
300         IStatement statement = null;
301         ResultSet JavaDoc set = null;
302
303         List JavaDoc list = new ArrayList JavaDoc();
304
305         try
306         {
307             connection = getConnection();
308
309             StatementAssembly assembly = new StatementAssembly();
310
311             assembly.newLine("SELECT PUBLISHER_ID, NAME");
312             assembly.newLine("FROM PUBLISHER");
313             assembly.newLine("ORDER BY NAME");
314
315             statement = assembly.createStatement(connection);
316
317             set = statement.executeQuery();
318
319             while (set.next())
320             {
321                 Integer JavaDoc primaryKey = (Integer JavaDoc) set.getObject(1);
322                 String JavaDoc name = set.getString(2);
323
324                 list.add(new Publisher(primaryKey, name));
325             }
326         }
327         catch (SQLException JavaDoc ex)
328         {
329             ex.printStackTrace();
330             throw new XEJBException("Could not fetch all Publishers.", ex);
331         }
332         finally
333         {
334             close(connection, statement, set);
335         }
336
337         // Convert from List to Publisher[]
338

339         return (Publisher[]) list.toArray(new Publisher[list.size()]);
340     }
341
342     /**
343      * Fetchs all {@link IPerson} beans in the database and converts them
344      * to {@link Person} objects.
345      *
346      * Returns the {@link Person}s sorted by last name, then first.
347      **/

348
349     public Person[] getPersons()
350     {
351         Connection JavaDoc connection = null;
352         IStatement statement = null;
353         ResultSet JavaDoc set = null;
354
355         List JavaDoc list = new ArrayList JavaDoc();
356
357         try
358         {
359             connection = getConnection();
360
361             StatementAssembly assembly = buildBasePersonQuery();
362             assembly.newLine("ORDER BY LAST_NAME, FIRST_NAME");
363
364             statement = assembly.createStatement(connection);
365
366             set = statement.executeQuery();
367
368             Object JavaDoc[] columns = new Object JavaDoc[Person.N_COLUMNS];
369
370             while (set.next())
371             {
372                 list.add(convertRowToPerson(set, columns));
373             }
374         }
375         catch (SQLException JavaDoc ex)
376         {
377             throw new XEJBException("Could not fetch all Persons.", ex);
378         }
379         finally
380         {
381             close(connection, statement, set);
382         }
383
384         return (Person[]) list.toArray(new Person[list.size()]);
385     }
386
387     /**
388      * Gets the {@link Person} for primary key.
389      *
390      * @throws FinderException if the Person does not exist.
391      **/

392
393     public Person getPerson(Integer JavaDoc personId) throws FinderException JavaDoc
394     {
395         Connection JavaDoc connection = null;
396         IStatement statement = null;
397         ResultSet JavaDoc set = null;
398
399         Person result = null;
400
401         try
402         {
403             connection = getConnection();
404
405             StatementAssembly assembly = buildBasePersonQuery();
406             assembly.newLine("WHERE ");
407             assembly.add("PERSON_ID = ");
408             assembly.addParameter(personId);
409             assembly.newLine("ORDER BY LAST_NAME, FIRST_NAME");
410
411             statement = assembly.createStatement(connection);
412
413             set = statement.executeQuery();
414
415             if (!set.next())
416                 throw new FinderException JavaDoc("Person #" + personId + " does not exist.");
417
418             Object JavaDoc[] columns = new Object JavaDoc[Person.N_COLUMNS];
419             result = convertRowToPerson(set, columns);
420
421         }
422         catch (SQLException JavaDoc ex)
423         {
424             throw new XEJBException("Unable to perform database query.", ex);
425         }
426         finally
427         {
428             close(connection, statement, set);
429         }
430
431         return result;
432     }
433
434     public Person login(String JavaDoc email, String JavaDoc password) throws RemoteException JavaDoc, LoginException
435     {
436         IPersonHome home = getPersonHome();
437         IPerson person = null;
438         Person result = null;
439
440         try
441         {
442             person = home.findByEmail(email);
443         }
444         catch (FinderException JavaDoc ex)
445         {
446             throw new LoginException("Unknown e-mail address.", false);
447         }
448
449         if (!person.getPassword().equals(password))
450             throw new LoginException("Invalid password.", true);
451
452         try
453         {
454             result = getPerson((Integer JavaDoc) person.getPrimaryKey());
455         }
456         catch (FinderException JavaDoc ex)
457         {
458             throw new LoginException("Could not read person.", false);
459         }
460
461         if (result.isLockedOut())
462             throw new LoginException("You have been locked out of the Virtual Library.", false);
463
464         // Set the last access time for any subsequent login.
465

466         person.setLastAccess(new Timestamp JavaDoc(System.currentTimeMillis()));
467
468         return result;
469     }
470
471     public Map JavaDoc getPersonAttributes(Integer JavaDoc personId) throws FinderException JavaDoc, RemoteException JavaDoc
472     {
473         IPersonHome home = getPersonHome();
474
475         IPerson person = home.findByPrimaryKey(personId);
476
477         return person.getEntityAttributes();
478     }
479
480     /**
481      * Retrieves a single {@link Book} by its primary key.
482      *
483      * @throws FinderException if the Book does not exist.
484      *
485      **/

486
487     public Book getBook(Integer JavaDoc bookId) throws FinderException JavaDoc
488     {
489         Connection JavaDoc connection = null;
490         IStatement statement = null;
491         ResultSet JavaDoc set = null;
492
493         Book result = null;
494
495         try
496         {
497             connection = getConnection();
498
499             StatementAssembly assembly = buildBaseBookQuery();
500             assembly.addSep(" AND ");
501             assembly.add("book.BOOK_ID = ");
502             assembly.addParameter(bookId);
503
504             statement = assembly.createStatement(connection);
505
506             set = statement.executeQuery();
507
508             if (!set.next())
509                 throw new FinderException JavaDoc("Book " + bookId + " does not exist.");
510
511             Object JavaDoc[] columns = new Object JavaDoc[Book.N_COLUMNS];
512             result = convertRowToBook(set, columns);
513
514         }
515         catch (SQLException JavaDoc ex)
516         {
517             throw new XEJBException("Unable to perform database query.", ex);
518         }
519         finally
520         {
521             close(connection, statement, set);
522         }
523
524         return result;
525     }
526
527     public Map JavaDoc getBookAttributes(Integer JavaDoc bookId) throws FinderException JavaDoc, RemoteException JavaDoc
528     {
529         IBookHome home = getBookHome();
530
531         IBook book = home.findByPrimaryKey(bookId);
532
533         return book.getEntityAttributes();
534     }
535
536     /**
537      * Attempts to register a new user, first checking that the
538      * e-mail and names are unique. Returns the primary key of the
539      * new {@link IPerson}.
540      *
541      **/

542
543     public Person registerNewUser(String JavaDoc firstName, String JavaDoc lastName, String JavaDoc email, String JavaDoc password)
544         throws RegistrationException, CreateException JavaDoc, RemoteException JavaDoc
545     {
546         IPersonHome home;
547
548         if (password == null || password.trim().length() == 0)
549             throw new RegistrationException("Must specify a password.");
550
551         validateUniquePerson(firstName, lastName, email);
552
553         home = getPersonHome();
554
555         Map JavaDoc attributes = new HashMap JavaDoc();
556
557         attributes.put("lastName", lastName.trim());
558         attributes.put("firstName", firstName.trim());
559         attributes.put("email", email.trim());
560         attributes.put("password", password.trim());
561         attributes.put("lastAccess", new Timestamp JavaDoc(System.currentTimeMillis()));
562
563         IPerson person = home.create(attributes);
564
565         Integer JavaDoc personId = (Integer JavaDoc) person.getPrimaryKey();
566
567         try
568         {
569             return getPerson(personId);
570         }
571         catch (FinderException JavaDoc ex)
572         {
573             throw new XCreateException("Unable to find newly created Person.", ex);
574         }
575     }
576
577     public Book deleteBook(Integer JavaDoc bookId) throws RemoveException JavaDoc, RemoteException JavaDoc
578     {
579         IBookHome home = getBookHome();
580         Book result = null;
581
582         try
583         {
584             result = getBook(bookId);
585         }
586         catch (FinderException JavaDoc ex)
587         {
588             throw new XRemoveException(ex);
589         }
590
591         home.remove(bookId);
592
593         return result;
594
595     }
596
597     /**
598      * Transfers a number of books to a new owner.
599      *
600      **/

601
602     public void transferBooks(Integer JavaDoc newOwnerId, Integer JavaDoc[] bookIds)
603         throws FinderException JavaDoc, RemoteException JavaDoc
604     {
605         if (bookIds == null)
606             throw new RemoteException JavaDoc("Must supply non-null list of books to transfer.");
607
608         if (newOwnerId == null)
609             throw new RemoteException JavaDoc("Must provide an owner for the books.");
610
611         // Verify that the new owner exists.
612

613         IPersonHome personHome = getPersonHome();
614         personHome.findByPrimaryKey(newOwnerId);
615
616         // Direct SQL would be more efficient, but this'll probably do.
617

618         IBookHome home = getBookHome();
619
620         for (int i = 0; i < bookIds.length; i++)
621         {
622             IBook book = home.findByPrimaryKey(bookIds[i]);
623
624             book.setOwnerId(newOwnerId);
625         }
626     }
627
628     public void updatePublishers(Publisher[] updated, Integer JavaDoc[] deleted)
629         throws FinderException JavaDoc, RemoveException JavaDoc, RemoteException JavaDoc
630     {
631         IPublisherHome home = getPublisherHome();
632
633         if (updated != null)
634         {
635             for (int i = 0; i < updated.length; i++)
636             {
637                 IPublisher publisher = home.findByPrimaryKey(updated[i].getId());
638                 publisher.setName(updated[i].getName());
639             }
640         }
641
642         if (deleted != null)
643         {
644             for (int i = 0; i < deleted.length; i++)
645             {
646                 home.remove(deleted[i]);
647             }
648         }
649     }
650
651     public void updatePersons(
652         Person[] updated,
653         Integer JavaDoc[] resetPassword,
654         String JavaDoc newPassword,
655         Integer JavaDoc[] deleted,
656         Integer JavaDoc adminId)
657         throws FinderException JavaDoc, RemoveException JavaDoc, RemoteException JavaDoc
658     {
659         IPersonHome home = getPersonHome();
660
661         int count = Tapestry.size(updated);
662
663         for (int i = 0; i < count; i++)
664         {
665             Person u = updated[i];
666             IPerson person = home.findByPrimaryKey(u.getId());
667
668             person.setAdmin(u.isAdmin());
669             person.setLockedOut(u.isLockedOut());
670         }
671
672         count = Tapestry.size(resetPassword);
673
674         for (int i = 0; i < count; i++)
675         {
676             IPerson person = home.findByPrimaryKey(resetPassword[i]);
677
678             person.setPassword(newPassword);
679         }
680
681         count = Tapestry.size(deleted);
682
683         if (count > 0)
684         {
685             returnBooksFromDeletedPersons(deleted);
686             moveBooksFromDeletedPersons(deleted, adminId);
687         }
688
689         for (int i = 0; i < count; i++)
690             home.remove(deleted[i]);
691     }
692
693     /**
694      * Invoked to update all books owned by people about to be deleted, to
695      * reassign the books holder back to the owner.
696      *
697      **/

698
699     private void returnBooksFromDeletedPersons(Integer JavaDoc deletedPersonIds[]) throws RemoveException JavaDoc
700     {
701         StatementAssembly assembly = new StatementAssembly();
702
703         assembly.add("UPDATE BOOK");
704         assembly.newLine("SET HOLDER_ID = OWNER_ID");
705         assembly.newLine("WHERE HOLDER_ID IN (");
706         assembly.addParameterList(deletedPersonIds, ", ");
707         assembly.add(")");
708
709         executeUpdate(assembly);
710     }
711
712     /**
713      * Invoked to execute a bulk update that moves books to the new admin.
714      *
715      **/

716
717     private void moveBooksFromDeletedPersons(Integer JavaDoc deletedPersonIds[], Integer JavaDoc adminId)
718         throws RemoveException JavaDoc
719     {
720         StatementAssembly assembly = new StatementAssembly();
721
722         assembly.add("UPDATE BOOK");
723         assembly.newLine("SET OWNER_ID = ");
724         assembly.addParameter(adminId);
725         assembly.newLine("WHERE OWNER_ID IN (");
726         assembly.addParameterList(deletedPersonIds, ", ");
727         assembly.add(")");
728
729         executeUpdate(assembly);
730
731     }
732
733     private void executeUpdate(StatementAssembly assembly) throws XRemoveException
734     {
735         Connection JavaDoc connection = null;
736         IStatement statement = null;
737
738         try
739         {
740             connection = getConnection();
741
742             statement = assembly.createStatement(connection);
743
744             statement.executeUpdate();
745
746             statement.close();
747             statement = null;
748
749             connection.close();
750             connection = null;
751         }
752         catch (SQLException JavaDoc ex)
753         {
754             throw new XRemoveException(
755                 "Unable to execute " + assembly + ": " + ex.getMessage(),
756                 ex);
757         }
758         finally
759         {
760             close(connection, statement, null);
761         }
762     }
763
764     /**
765      * Translates the next row from the result set into a {@link Book}.
766      *
767      * <p>This works with queries generated by {@link #buildBaseBookQuery()}.
768      *
769      **/

770
771     protected Book convertRowToBook(ResultSet JavaDoc set, Object JavaDoc[] columns) throws SQLException JavaDoc
772     {
773         int column = 1;
774
775         columns[Book.ID_COLUMN] = set.getObject(column++);
776         columns[Book.TITLE_COLUMN] = set.getString(column++);
777         columns[Book.DESCRIPTION_COLUMN] = set.getString(column++);
778         columns[Book.ISBN_COLUMN] = set.getString(column++);
779         columns[Book.OWNER_ID_COLUMN] = set.getObject(column++);
780         columns[Book.OWNER_NAME_COLUMN] =
781             buildName(set.getString(column++), set.getString(column++));
782         columns[Book.HOLDER_ID_COLUMN] = set.getObject(column++);
783         columns[Book.HOLDER_NAME_COLUMN] =
784             buildName(set.getString(column++), set.getString(column++));
785         columns[Book.PUBLISHER_ID_COLUMN] = set.getObject(column++);
786         columns[Book.PUBLISHER_NAME_COLUMN] = set.getString(column++);
787         columns[Book.AUTHOR_COLUMN] = set.getString(column++);
788         columns[Book.HIDDEN_COLUMN] = getBoolean(set, column++);
789         columns[Book.LENDABLE_COLUMN] = getBoolean(set, column++);
790         columns[Book.DATE_ADDED_COLUMN] = set.getTimestamp(column++);
791
792         return new Book(columns);
793     }
794
795     private String JavaDoc buildName(String JavaDoc firstName, String JavaDoc lastName)
796     {
797         if (firstName == null)
798             return lastName;
799
800         return firstName + " " + lastName;
801     }
802
803     /**
804      * All queries must use this exact set of select columns, so that
805      * {@link #convertRow(ResultSet, Object[])} can build
806      * the correct {@link Book} from each row.
807      *
808      **/

809
810     private static final String JavaDoc[] BOOK_SELECT_COLUMNS =