View Javadoc

1   /**
2    * @version $Revision$ $Date$
3    *
4    */
5   package org.sourceforge.vlibrary.user.logic;
6   
7   import java.util.ArrayList;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.Locale;
11  import java.util.Map;
12  
13  import org.apache.log4j.Logger;
14  import org.sourceforge.vlibrary.Constants;
15  import org.sourceforge.vlibrary.exceptions.LibraryException;
16  import org.sourceforge.vlibrary.user.dao.AuthorDAO;
17  import org.sourceforge.vlibrary.user.dao.BookDAO;
18  import org.sourceforge.vlibrary.user.dao.LibraryTransactionDAO;
19  import org.sourceforge.vlibrary.user.dao.LocationDAO;
20  import org.sourceforge.vlibrary.user.dao.ReaderDAO;
21  import org.sourceforge.vlibrary.user.dao.SubjectDAO;
22  import org.sourceforge.vlibrary.user.domain.Author;
23  import org.sourceforge.vlibrary.user.domain.Book;
24  import org.sourceforge.vlibrary.user.domain.Location;
25  import org.sourceforge.vlibrary.user.domain.Reader;
26  import org.sourceforge.vlibrary.user.domain.Subject;
27  import org.sourceforge.vlibrary.user.exceptions.BookAwaitingPickupException;
28  import org.sourceforge.vlibrary.user.exceptions.BookNotFoundException;
29  import org.sourceforge.vlibrary.user.exceptions.DuplicateRequestException;
30  import org.sourceforge.vlibrary.user.exceptions.ReaderNotFoundException;
31  import org.sourceforge.vlibrary.user.valuebeans.BookMoveTransaction;
32  import org.sourceforge.vlibrary.user.valuebeans.LibraryTransaction;
33  import org.sourceforge.vlibrary.user.workers.MailWorker;
34  import org.sourceforge.vlibrary.util.Crypto;
35  import org.sourceforge.vlibrary.util.SruSrwClientInterface;
36  import org.springframework.context.support.ResourceBundleMessageSource;
37  
38  /**
39   * Implementation of {@link LibraryManagerFacade} interface providing
40   * the business logic functions for the Virtual Library. Data access is
41   * mediated by DAO objects, which are instantiated using Spring dependency
42   * injection.
43   *
44   */
45  
46  public class LibraryManager {
47      private ResourceBundleMessageSource resourceBundleMessageSource;
48  
49      /** log4j category */
50      private static Logger logger =
51              Logger.getLogger(LibraryManager.class.getName());
52  
53      // DAO instances - injected by Spring
54      private AuthorDAO authorDAO;
55      private BookDAO bookDAO;
56      private ReaderDAO readerDAO;
57      private SubjectDAO subjectDAO;
58      private LocationDAO locationDAO;
59      private LibraryTransactionDAO libraryTransactionDAO;
60      private MailWorker mailWorker;
61      private Crypto crypto;
62      private SruSrwClientInterface sruClient;
63  
64  
65      //-------------------------------------------------------------------------
66      // Setter methods for dependency injection
67      //-------------------------------------------------------------------------
68  
69      /**
70       * Used for Spring Dependency Injection
71       */
72      public void setResourceBundleMessageSource(
73              ResourceBundleMessageSource resourceBundleMessageSource) {
74          this.resourceBundleMessageSource = resourceBundleMessageSource;
75      }
76  
77      /**
78       * @param mailWorker The mailWorker to set.
79       */
80      public void setMailWorker(MailWorker mailWorker) {
81          this.mailWorker = mailWorker;
82      }
83  
84      /**
85       * @param authorDAO The authorDAO to set.
86       */
87      public void setAuthorDAO(AuthorDAO authorDAO) {
88          this.authorDAO = authorDAO;
89      }
90  
91      /**
92       * @param bookDAO The bookDAO to set.
93       */
94      public void setBookDAO(BookDAO bookDAO) {
95          this.bookDAO = bookDAO;
96      }
97  
98      /**
99       * @param readerDAO The readerDAO to set.
100      */
101     public void setReaderDAO(ReaderDAO readerDAO) {
102         this.readerDAO = readerDAO;
103     }
104 
105     public ReaderDAO getReaderDAO() {
106         return this.readerDAO;
107     }
108 
109     /**
110      * @param subjectDAO The subjectDAO to set.
111      */
112     public void setSubjectDAO(SubjectDAO subjectDAO) {
113         this.subjectDAO = subjectDAO;
114     }
115 
116     /**
117      * @param libraryTransactionDAO The libraryTransactionDAO to set.
118      */
119     public void setLibraryTransactionDAO(
120             LibraryTransactionDAO libraryTransactionDAO) {
121         this.libraryTransactionDAO = libraryTransactionDAO;
122     }
123 
124     /**
125      * Used for Spring Dependency Injection
126      */
127     public void setCrypto( Crypto crypto) {
128         this.crypto = crypto;
129     }
130 
131     /**
132      * @param subjectDAO The subjectDAO to set.
133      */
134     public void setLocationDAO(LocationDAO locationDAO) {
135         this.locationDAO = locationDAO;
136     }
137 
138     /**
139      * Used for Spring Dependency Injection
140      */
141     public void setSruSrwClientInterface( SruSrwClientInterface sruClient) {
142         this.sruClient = sruClient;
143     }
144 
145     //-------------------------------------------------------------------------
146     // Operation methods
147     //-------------------------------------------------------------------------
148 
149     /**
150      * Returns an ArrayList including all subjects currently defined in the
151      * Virtual Library.
152      *
153      * @return list of currently defined subjects
154      * @throws LibraryException if an error occurs retrieving the list of
155      * subjects
156      */
157     public ArrayList<Subject> getSubjects() throws LibraryException {
158         if (logger.isDebugEnabled()) {
159             logger.debug("");
160         }
161 
162         return subjectDAO.getSubjects();
163     }
164 
165     /**
166      * Walks the input ArrayList of {@link org.sourceforge.vlibrary.user.domain.Subject} instances, looking up and
167      * setting ID properties for each Subject in the list.  Lookup is based on
168      * the description property. If a Subject is not found, a new entry is
169      * inserted into the database and the ID of the newly created record is
170      * assigned to the instance.
171      *
172      * @param subjects ArrayList of Subjects
173      * @exception LibraryException if a data access error occurs
174      */
175     public void createSubjectIds(ArrayList<Subject> subjects) throws LibraryException {
176         if (logger.isDebugEnabled()) {
177             logger.debug(subjects);
178         }
179 
180         subjectDAO.setSubjectIds(subjects);
181     }
182 
183     /**
184      * Returns an ArrayList including all currently defined locations.
185      *
186      * @return list of currently defined locations
187      * @throws LibraryException if an error occurs retrieving the list of
188      * subjects
189      */
190     public ArrayList<Location> getLocations() throws LibraryException {
191         if (logger.isDebugEnabled()) {
192             final String message = resourceBundleMessageSource.getMessage("retrieving.locations",
193                     new Object[] { new String("") }, Locale.US);
194             logger.debug(message);
195         }
196         try {
197             return locationDAO.getLocations();
198         } catch (final Exception ex) {
199             final String errString = resourceBundleMessageSource.getMessage("error.retrieving.locations",
200                     new Object[] { new String("") }, Locale.US);
201             logger.error(errString, ex);
202             throw new LibraryException(errString);
203         }
204 
205     }
206 
207     /**
208      * Determines whether or not the supplied desk phone number belongs to a
209      * reader.
210      *
211      * @param phoneNo  phone number to validate
212      * @return true if the phone mumber belongs to a Virtual Library reader
213      * @throws LibraryException if a data access error occurs validating
214      * the phone number
215      */
216     public boolean isUserValidByPhone(String phoneNo) throws LibraryException {
217         if (logger.isDebugEnabled()) {
218             logger.debug("phoneNo="+phoneNo);
219         }
220 
221         final Reader rd = readerDAO.retrieveByPhone(phoneNo);
222 
223         if (rd == null) {
224             return false;
225         } else {
226             return true;
227         }
228     }
229 
230     /**
231      * <p>Returns the ID of the reader who has the given desk phone.
232      * </p>
233      * Returns <code>0</code> if there is no reader with the given desk
234      * phone.
235      *
236      * @param phoneNo the desk phone to look up
237      * @return the ID of the reader, or 0 if not found
238      * @throws LibraryException if a data access error occurs
239      */
240     public long retrieveIDByPhone(String phoneNo) throws LibraryException {
241         if (logger.isDebugEnabled()) {
242             logger.debug("phoneNo=" + phoneNo);
243         }
244 
245         Reader rd = new Reader();
246 
247         rd = readerDAO.retrieveByPhone(phoneNo);
248 
249         if ( rd == null) {
250             return 0;
251         }
252 
253         return rd.getId();
254     }
255 
256     /**
257      * Determines whether or not the given ID corresponds to a reader.
258      *
259      * @param id ID to validate
260      * @return true if the ID belongs to a reader; false otherwiase
261      * @throws LibraryException if a data access error occurs looking up the ID
262      */
263     public boolean isUserIDValid(long id) throws LibraryException {
264         if (logger.isDebugEnabled()) {
265             logger.debug("userId" + id);
266         }
267 
268         return readerDAO.readerExists(id);
269     }
270 
271     /**
272      * Determines whether or not the given UID (user id) belongs to a reader.
273      *
274      * @param id UID to validate
275      * @return true if the UID belongs to a reader; false otherwise
276      * @throws LibraryException if a data access error occurs looking up
277      * the UID
278      */
279     public boolean isUserIDValid(String UID)
280             throws LibraryException {
281         if (logger.isDebugEnabled()) {
282             logger.debug("userId" + UID);
283         }
284 
285         return readerDAO.uidExists(UID);
286     }
287 
288     /**
289      * Determines whether or not there is a reader with the given first and
290      * last name.
291      *
292      * @param firstName first name
293      * @param lastName  last name
294      * @return true if there is a reader with the given first and last name;
295      * false otherwise
296      * @throws LibraryException if a data access error occurs
297      */
298     public boolean readerExists(String firstName, String lastName)
299             throws LibraryException {
300         if (logger.isDebugEnabled()) {
301             logger.debug(
302                     "firstName="+firstName + ", lastName=" + lastName);
303         }
304 
305         return readerDAO.readerExists( firstName, lastName);
306     }
307 
308     /**
309      * Determines whether or not there is a reader with the given ID.
310      *
311      * @param id the ID to validate
312      * @return true if there is a reader with the given ID;
313      * false otherwise
314      * @throws LibraryException if a data access error occurs
315      */
316     public boolean readerExists(long id) throws LibraryException {
317         if (logger.isDebugEnabled()) {
318             logger.debug("reader="+id);
319         }
320 
321         return readerDAO.readerExists(id);
322     }
323 
324     /**
325      * Determines whether or not there is a reader with the given
326      * desk phone.
327      *
328      * @param deskPhone the phone number to validate
329      * @return true if there is a reader with the given desk phone;
330      * false otherwise
331      * @throws LibraryException if a data access error occurs
332      */
333     public boolean deskPhoneExists(String deskPhone) throws LibraryException {
334         if (logger.isDebugEnabled()) {
335             logger.debug("deskPhone="+deskPhone);
336         }
337 
338         return readerDAO.deskPhoneExists(deskPhone);
339     }
340 
341     /**
342      * Returns an <code>ArrayList</code> of {@link Reader} instances including
343      * all currently registered Virtual Library readers.
344      *
345      * @return all readers in the database
346      * @throws LibraryException if a data access error occurs
347      */
348     public ArrayList<Reader> getReaders() throws LibraryException {
349         if (logger.isDebugEnabled()) {
350             logger.debug("");
351         }
352 
353         return readerDAO.getReaders();
354     }
355 
356     /**
357      * Returns the {@link LibraryTransaction} with the given transaction id.
358      *
359      * @param id the ID of the transaction to retrieve
360      * @throws LibraryException if a data access error occurs retrieving the
361      * transaction
362      */
363     public LibraryTransaction retrieveTransaction(long id)
364             throws LibraryException {
365         if (logger.isDebugEnabled()) {
366             logger.debug("tranId="+id);
367         }
368 
369         return libraryTransactionDAO.retrieve(id );
370     }
371 
372     /**
373      * Returns a list including all {org.sourceforge.vlibrary.user.domain.Author}s with the the given
374      * last name.
375      *
376      * @param authorLastName the last name to look up
377      * @return list of author instances
378      * @throws LibraryException if a data access error occurs
379      */
380     public ArrayList<Book> authorSearch(String authorLastName)
381             throws LibraryException {
382         if (logger.isDebugEnabled()) {
383             logger.debug("authorLastName="+authorLastName);
384         }
385 
386         return bookDAO.authorSearch( authorLastName );
387     }
388 
389     /**
390      * Returns an ArrayList of {@link Book} instances consisting of all books
391      * associated with one or more of the {@link org.sourceforge.vlibrary.user.domain.Subject}s in the input array.
392      *
393      * @param subjects list of subjects
394      * @return list of associated books
395      * @throws LibraryException if a data access error occurs
396      */
397     public ArrayList<Book> subjectOrSearch(ArrayList<Subject> subjects)
398             throws LibraryException {
399         if (logger.isDebugEnabled()) {
400             logger.debug(subjects);
401         }
402 
403         return bookDAO.subjectOrSearch( subjects );
404     }
405 
406     /**
407      * Returns an ArrayList of {@link Book} instances consisting of all books
408      * that are associated with <strong>all</strong> of the {@link org.sourceforge.vlibrary.user.domain.Subject}s
409      * in the input array.
410      *
411      * @param subjects list of subjects
412      * @return list of associated books
413      * @throws LibraryException if a data access error occurs
414      */
415     public ArrayList<Book> subjectAndSearch(ArrayList subjects)
416             throws LibraryException {
417         if (logger.isDebugEnabled()) {
418             logger.debug(subjects);
419         }
420 
421         return bookDAO.subjectAndSearch(subjects);
422     }
423 
424     /**
425      * Returns an ArrayList of {@link Book} instances consisting of all books
426      * that are owned by the reader with the given ID.
427      *
428      * @param owner ID of owner
429      * @return list of books owned
430      * @throws LibraryException if a data access error occurs
431      */
432     public ArrayList<Book> ownerSearch(long owner)
433             throws LibraryException {
434         if (logger.isDebugEnabled()) {
435             logger.debug("owner="+owner);
436         }
437 
438         return bookDAO.ownerSearch( owner );
439     }
440 
441     /**
442      * Returns an ArrayList of {@link Book} instances consisting of all books
443      * having the given title.
444      *
445      * @param title title to look for
446      * @return list of books with the given title
447      * @throws LibraryException if a data access error occurs
448      */
449     public  ArrayList<Book> titleSearch(String title)
450             throws LibraryException {
451         if (logger.isDebugEnabled()) {
452             logger.debug("bookTitle="+title);
453         }
454 
455         return bookDAO.titleSearch( title );
456     }
457 
458     /**
459      * Returns an ArrayList of {@link Book} instances consisting of all books
460      * in the library.
461      *
462      * @return list of all books
463      * @throws LibraryException if a data access error occurs
464      */
465     public ArrayList<Book> dump()
466             throws LibraryException {
467         if (logger.isDebugEnabled()) {
468             logger.debug("");
469         }
470 
471         return bookDAO.dump();
472     }
473 
474     /**
475      * <p>Retrieves a {@link Reader} by user id (UID).
476      * </p>
477      * <p>Returns <code>null</code> if the supplied uid is not associated
478      * with a reader in the datablase.
479      *
480      * @param uid the UID of the reader to lookup
481      * @return the reader with the given uid; or null if there is no such
482      * reader
483      * @throws LibraryException if a data access error occurs
484      */
485     public Reader retrieveByUid(String uid) throws LibraryException {
486         if (logger.isDebugEnabled()) {
487             logger.debug("readerUid="+uid);
488         }
489 
490         return readerDAO.retrieveByUid( uid );
491     }
492 
493     /**
494      * Returns an ArrayList of {@link }Author} instances including the authors
495      * of the given book.
496      *
497      * @param book the book
498      * @return list of authors of the book
499      * @throws LibraryException if a data access error occurs
500      */
501     public ArrayList<Author> getAuthors(Book book)
502             throws LibraryException {
503         if (logger.isDebugEnabled()) {
504             logger.debug(book.toString());
505         }
506 
507         return bookDAO.getAuthors(book);
508     }
509 
510     /**
511      * Returns an ArrayList including all subjects currently defined in the
512      * Virtual Library.
513      *
514      * @return list of currently defined subjects
515      * @throws LibraryException if an error occurs retrieving the list of
516      * subjects
517      */
518     public ArrayList<Subject> getSubjects(Book book)
519             throws LibraryException {
520         if (logger.isDebugEnabled()) {
521             logger.debug(book.toString());
522         }
523 
524         return bookDAO.getSubjects(book);
525     }
526 
527 
528     /**
529      * Processes a book checkin or checkout transaction. Records the
530      * transaction in the transaction table and sends appropriate
531      * email notifications. Returns a {@link BookMoveTransaction} representing
532      * the transaction.
533      * <p>
534      * Delegates to one of {@link #processCheckin(long, long)} or
535      * {@link #processCheckout(long, long)}, depending on the
536      * <code>transactionType</code>, which must be either
537      * <code>Contants.CHECKIN</code> or <code>Contants.CHECKOUT</code>.
538      * </p>
539      *
540      * @param reader the id of the reader initiating the transaction
541      * @param book the id of the book being checked in or out
542      * @param transactionType transaction type - must be either
543      * <code>Contants.CHECKIN</code> or <code>Contants.CHECKOUT</code>
544      * @param location the location of the checkin or checkout
545      * @return the resulting transaction
546      * @throws LibraryException if an error occurs processing the transaction,
547      * or if the parameters are not valid
548      */
549     public BookMoveTransaction processExchange(long reader, long book, String transactionType, long location)
550             throws LibraryException {
551         if (logger.isDebugEnabled()) {
552             String message;
553             if (transactionType.equals(Constants.CHECKIN)) {
554                 message = resourceBundleMessageSource.getMessage("trans.checking.in", new Object[] {
555                         (new Long(reader)).toString(), (new Long(book)).toString(), Long.toString(location) },
556                         Locale.US);
557             } else {
558                 message = resourceBundleMessageSource.getMessage("trans.checking.out", new Object[] {
559                         (new Long(reader)).toString(), (new Long(book)).toString(), Long.toString(location) },
560                         Locale.US);
561             }
562             logger.debug(message);
563         }
564 
565         Book bk = new Book();
566         bk.setId(book);
567 
568         try {
569             bk = bookDAO.retrieve(book);
570             if (bk == null) {
571                 throw new Exception();
572             }
573         } catch (final Throwable e) {
574             final String errString = resourceBundleMessageSource.getMessage("error.book.retrieve",
575                     new Object[] { bk == null ? "" : bk.toString() }, Locale.US);
576             logger.error(errString, e);
577             throw new LibraryException(errString, e);
578         }
579 
580         Reader rd = new Reader();
581         try {
582             rd = readerDAO.retrieveById(reader);
583             if (rd == null) {
584                 throw new Exception();
585             }
586         } catch (final Throwable e) {
587             final String errString = resourceBundleMessageSource.getMessage("error.reader.retrieve",
588                     new Object[] { rd == null ? "" : rd.toString() }, Locale.US);
589             logger.error(errString, e);
590             throw new LibraryException(errString, e);
591         }
592 
593         // Fire transaction -- checkin or checkout
594         try {
595             if (transactionType.equals(Constants.CHECKIN)) {
596                 processCheckin(reader, book, location);
597             } else if (transactionType.equals(Constants.CHECKOUT)) {
598                 processCheckout(reader, book, location);
599             } else {
600                 final String errString = resourceBundleMessageSource.getMessage("error.trans.request.badrequest",
601                         new Object[] { (new Long(reader)).toString(), (new Long(book)).toString() }, Locale.US);
602                 logger.error(errString);
603                 throw new LibraryException(errString);
604             }
605         } catch (final Throwable e) {
606             final String errString = resourceBundleMessageSource.getMessage("error.trans.request.badrequest",
607                     new Object[] { (new Long(reader)).toString(), (new Long(book)).toString() }, Locale.US);
608             logger.error(errString, e);
609             throw new LibraryException(e.getMessage(), e);
610         }
611 
612         // Put a BookMoveTransaction VO in the request for confirmation page
613         final BookMoveTransaction t = new BookMoveTransaction();
614         t.setReaderEmail(rd.getEmail());
615         t.setReaderName(rd.getFirstName() + " " + rd.getLastName());
616         t.setBookTitle(bk.getTitle());
617         t.setTransaction(transactionType);
618         t.setLocation(location);
619 
620         if (logger.isDebugEnabled()) {
621             final String message = resourceBundleMessageSource.getMessage("trans.checkin.successful", new Object[] {
622                     (new Long(reader)).toString(), (new Long(book)).toString(), Long.toString(location) }, Locale.US);
623             logger.debug(message);
624         }
625 
626         return t;
627     }
628 
629     /**
630      * Determines whether or not there is a book with the given id.
631      *
632      * @param book id to check
633      * @return true if there is a book with the given id; false otherwise
634      */
635     public boolean bookExists(long book)
636             throws LibraryException {
637         if (logger.isDebugEnabled()) {
638             logger.debug("book="+book);
639         }
640 
641         return bookDAO.bookExists(book);
642     }
643 
644     /**
645      * <p>Returns the ID of a reader with the given first and last name, if
646      * there is such a reader, throwing <code>LibraryException</code> if
647      * there is no reader with the given name.
648      * </p>
649      * <p>Does not check for uniqueness - i.e., if there is more than one
650      * reader with the given first and last name, no exception is generated.
651      * </p>
652      *
653      * @param firstName the first name of the reader
654      * @param lastName the last name of the reader
655      * @return the ID of a reader with the given name
656      * @throws LibraryException if no such reader exists or a data access
657      * error occurs looking up the reader
658      *
659      */
660     public long getReaderID(String firstName,String lastName)
661             throws LibraryException {
662         if (logger.isDebugEnabled()) {
663             logger.debug(
664                     "firstname=" +firstName + ", lastName=" + lastName);
665         }
666 
667         return readerDAO.getReaderID( firstName, lastName );
668     }
669 
670     /**
671      * Walks input ArrayList, looking up and setting ID properties for
672      * each Author in the list.  Lookup is based on first name + last name.
673      * If an Author is not found, a new entry is inserted into the database
674      * and the ID of the newly created record is assigned to the instance.
675      *
676      * @param authors = ArrayList of Authors
677      * @exception LibraryException
678      */
679     public void createAuthorIds(ArrayList<Author> authors) throws LibraryException {
680         if (logger.isDebugEnabled()) {
681             logger.debug(authors);
682         }
683 
684         authorDAO.setAuthorIds(authors);
685     }
686 
687     /**
688      * Returns the book with the given ID, or <code>null</code> if there is no
689      * such book.
690      *
691      * @param bookID the ID of the book
692      * @return the <code>Book</code> instance with the given ID
693      * @throws LibraryException if a data access error occurs
694      */
695     public Book retrieveBook(long bookID) throws LibraryException {
696         if (logger.isDebugEnabled()) {
697             logger.debug("book=" + bookID);
698         }
699 
700         return bookDAO.retrieve(bookID);
701     }
702 
703     /**
704      * Returns a fully populated <code>Reader</code> corresponding to the
705      * reader having the given ID. Returns <code>null</code> if there is no
706      * such reader.
707      *
708      * @param id the ID to lookup
709      * @return fully populated <code>Reader</code> instance
710      * @throws LibraryException f a data access error occurs
711      */
712     public Reader retrieveReader( long id ) throws LibraryException {
713         if (logger.isDebugEnabled()) {
714             logger.debug("reader=" + id);
715         }
716 
717         return readerDAO.retrieveById( id );
718     }
719 
720     /**
721      * Sets the authors for the given book. Creates author records in the
722      * database for any authors that do not exist and associates the given
723      * list of authors with the book. Updates IDs of authors in the list to
724      * match database entries for those that already exist in the database.
725      *
726      * @param book the book to attach authors to
727      * @param authors the authors of the book
728      * @throws LibraryException if an error occurs accessing the database or
729      * the ID of the given Book does not correspond to a book in the database
730      */
731     public void createBookAuthors(Book book, ArrayList<Author> authors)
732             throws LibraryException {
733         if (logger.isDebugEnabled()) {
734             logger.debug(book.toString() + authors);
735         }
736 
737         bookDAO.setAuthors( book, authors );
738     }
739 
740     /**
741      * Sets the subjects for the given book. Creates subject records in the
742      * database for any authors that do not exist and associates the given
743      * list of authors with the book. Updates IDs of authors in the list to
744      * match database entries for those that already exist in the database.
745      *
746      * @param book the book to attach subjects to
747      * @param subjects the subjects of the book
748      * @throws LibraryException if an error occurs accessing the database or
749      * the ID of the given Book does not correspond to a book in the database
750      */
751     public void createBookSubjects(Book book, ArrayList<Subject> subjects)
752             throws LibraryException {
753         if (logger.isDebugEnabled()) {
754             logger.debug(book.toString() +  subjects);
755         }
756 
757         bookDAO.setSubjects( book, subjects );
758     }
759 
760     /**
761      * <p>Creates a book in the database and associates the given lists of
762      * subjects and authors with the book. Authors and subjects are created
763      * in the database if they do not already exist. Returns a new
764      * <code>Book</code> instance with the ID set to the database ID of the
765      * newly created book.
766      * </p>
767      * <p>The supplied book must have minimally a title and isbn, otherwise
768      * a LibraryException is thrown.
769      * </p>
770      *
771      * @param book the book to create
772      * @param subjects the subjects to associate with the book
773      * @param authors the authors of the book
774      * @return a <code>Book</code> instance with the ID of the newly created
775      * book
776      * @throws LibraryException if the supplied book does not have sufficient
777      * data or a database access error occurs
778      */
779     public Book createBook(Book book, ArrayList<Subject> subjects, ArrayList<Author> authors)
780             throws LibraryException {
781         if (logger.isDebugEnabled()) {
782             logger.debug(book.toString() + subjects + authors);
783         }
784         // Create the book in the database and set the id to the db-generated ID
785         book = bookDAO.insert(book);
786         // Associate the authors and subjects with the book
787         bookDAO.setAuthors(book, authors);
788         bookDAO.setSubjects(book, subjects);
789         return book;
790     }
791 
792     /**
793      * Updates the database record associated with <code>book</code> with the
794      * properties that the given instance has.  Uses the ID property to locate
795      * the record to update.  If the ID does not correspond to a book in the
796      * database, a <code>LibraryException</code> is thrown.
797      *
798      * @param book the book to update
799      * @throws LibraryException if the book does not exist, or a data access
800      * error occurs
801      */
802     public void updateBook(Book book) throws
803     LibraryException {
804         if (logger.isDebugEnabled()) {
805             logger.debug(book.toString());
806         }
807 
808         bookDAO.update( book );
809     }
810 
811     /**
812      * <p>Inserts a record corresponding to the given reader into the database.
813      * </p>
814      * <p>Ignores the ID field on input, but sets this field to the value of the
815      * database ID of the newly created reader.</p>
816      * <p>Throws <code>LibraryException</code> if the reader is missing
817      * first name, last name, email or desk phone properties; or if the email
818      * or desk phone already exist in the database.</p>
819      *
820      * @param reader the reader to add
821      * @throws LibraryException if the reader is a duplicate, is missing data,
822      * or a data access error occurs
823      */
824     public void insertReader(Reader reader)
825             throws LibraryException {
826         if (logger.isDebugEnabled()) {
827             logger.debug( reader.toString() );
828         }
829 
830         readerDAO.insert(reader);
831     }
832 
833     /**
834      * Updates the database record associated with <code>reader</code> with the
835      * properties that the given instance has.  Uses the ID property to locate
836      * the record to update.  If the ID does not correspond to a reader in the
837      * database, a <code>LibraryException</code> is thrown.
838      *
839      * @param reader the reader to update
840      * @throws LibraryException if the reader does not exist, or a data access
841      * error occurs
842      */
843     public void updateReader(Reader reader)
844             throws LibraryException {
845         if (logger.isDebugEnabled()) {
846             logger.debug(reader.toString());
847         }
848 
849         readerDAO.update(reader);
850     }
851 
852     /**
853      * <p>Processes book checkin transaction.
854      * </p>
855      * <p>Records the checkin in the transaction table and sends email
856      * notifications to current requesters.
857      * </p>
858      * <p>Allows redundant checkins - i.e., a book that is already checked in
859      * can be checked in again.</p>
860      *
861      * @param book the id of the book being checked in
862      * @param reader the id of the reader checking in the book
863      * @param location the id of the location of the transaction
864      * @throws LibraryException if an error occurs recording the checkin or
865      * sending notifications
866      */
867     public void processCheckin(long reader, long book, long location) throws LibraryException {
868         if (logger.isInfoEnabled()) {
869             final String message = resourceBundleMessageSource.getMessage("trans.checking.in", new Object[] {
870                     (new Long(reader)).toString(), (new Long(book)).toString(), Long.toString(location) }, Locale.US);
871             logger.info(message);
872         }
873         libraryTransactionDAO.processCheckin(reader, book, location);
874 
875         // send mail to current requesters
876         Book bk = new Book();
877         bk.setId(book);
878         bk = retrieveBook(book);
879         final ArrayList<Reader> requestors = getRequestors(book, location);
880 
881         for (int i = 0; i < requestors.size(); i++) {
882             final Reader rd = requestors.get(i);
883             try {
884                 mailWorker.sendReturnNotification(rd, bk, locationDAO.getLocationDescription(location));
885             } catch (final Exception ex) {
886                 final String errString = resourceBundleMessageSource.getMessage("error.trans.checkin",
887                         new Object[] { (new Long(reader)).toString(), (new Long(book)).toString() }, Locale.US);
888 
889                 logger.error(errString, ex);
890             }
891         }
892 
893         // log the transaction
894         if (logger.isInfoEnabled()) {
895             logger.info(resourceBundleMessageSource.getMessage("trans.checkin.successful",
896                     new Object[] { Long.toString(reader), Long.toString(book), Long.toString(location) }, Locale.US));
897         }
898     }
899 
900 
901     /**
902      * <p>Processes book checkout transaction.
903      * </p>
904      * <p>Records the checkout in the transaction table and sends email
905      * notifications to current requesters.
906      * </p>
907      *
908      * @param book the id of the book being checked out
909      * @param reader the id of the reader checking out the book
910      * @param location the id of the location of the transaction
911      * @throws LibraryException if an error occurs recording the checkout or
912      * sending notifications
913      */
914     public void processCheckout(long reader, long book, long location)
915             throws LibraryException {
916 
917         // log the transaction
918         if (logger.isDebugEnabled()) {
919             logger.info( resourceBundleMessageSource.getMessage(
920                     "trans.checking.out",
921                     new Object[] {(new Long(reader)).toString(),
922                             (new Long(book)).toString()},
923                     Locale.US));
924         }
925 
926         // verify that the reader and the book both exist
927         if (!readerDAO.readerExists(reader)) {
928             final String errString = resourceBundleMessageSource.getMessage(
929                     "error.nonexistent.reader",
930                     new Object[] {  new Long(reader) },
931                     Locale.US);
932             logger.error(errString);
933             throw new ReaderNotFoundException(errString);
934         }
935 
936         if (!bookExists(book)) {
937             final String errString = resourceBundleMessageSource.getMessage(
938                     "error.nonexistent.book",
939                     new Object[] {  new Long(book) },
940                     Locale.US);
941             logger.error(errString);
942             throw new BookNotFoundException(errString);
943         }
944 
945         // Construct newPossessor Reader object
946         Reader newPossessor = new Reader();
947         newPossessor.setId(reader);
948         newPossessor = readerDAO.retrieve(newPossessor);
949 
950         // Perform checkout and cancel any requests that the reader has for <book.isbn, location>
951         libraryTransactionDAO.processCheckout(reader, book, bookDAO.getIsbn(book), location);
952 
953         // send mail to current requesters
954         final Book bk = bookDAO.retrieve(book);
955         final ArrayList<Reader> requestors = getRequestors(book, location);
956         for (int i=0; i<requestors.size(); i++) {
957             final Reader requestor = requestors.get(i);
958             try {
959                 mailWorker.sendCheckoutNotification(requestor,newPossessor, bk,
960                         locationDAO.getLocationDescription(location));
961             } catch( final Exception ex) {
962                 final String errString =
963                         resourceBundleMessageSource.getMessage(
964                                 "error.trans.checkout",
965                                 new Object[] {(new Long(reader)).toString(),
966                                         (new Long(book)).toString()},
967                                 Locale.US);
968 
969                 logger.error(errString, ex);
970             }
971         }
972 
973         // log the transaction
974         if (logger.isDebugEnabled()) {
975             logger.info(resourceBundleMessageSource
976                     .getMessage("trans.checkout.successful",
977                             new Object[] {
978                                     Long.toString(reader), Long.toString(book),
979                                     Long.toString(location)
980                     }, Locale.US));
981         }
982     }
983 
984     /**
985      * Queries the transaction table to determine the whereabouts of a book.
986      * Returns the id of the reader who is currently holding the book.
987      * If the book is "checked in", returns <code>CHECKED_IN.</code>
988      *
989      * <p>If there are no transactions associated with the book,
990      * then the book's owner is assumed to have the book, so the
991      * owner's ID is returned in this case.</p>
992      *
993      * @param book the id of the book being sought
994      * @exception LibraryException will be thrown if the book does not exist
995      * or a data access error occurs
996      */
997     public long getPossessor(long book)
998             throws LibraryException {
999         if (logger.isDebugEnabled()) {
1000             logger.info( resourceBundleMessageSource.getMessage(
1001                     "retrieving.book.possesor",
1002                     new Object[] {(new Long(book)).toString()},
1003                     Locale.US));
1004         }
1005 
1006         if (!bookExists(book)) {
1007             final String errString = resourceBundleMessageSource.getMessage(
1008                     "error.nonexistent.book",
1009                     new Object[] {  new Long(book) },
1010                     Locale.US);
1011             logger.error(errString);
1012             throw new BookNotFoundException(errString);
1013         }
1014 
1015         final long result = libraryTransactionDAO.getPossessor(book);
1016 
1017         if (result == Constants.CHECKED_IN || readerExists(result)) {
1018             return result;
1019         }
1020 
1021         // No checkout/checkin transactions, so assume owner has the book
1022         Book bk = new Book();
1023         bk.setId(book);
1024         bk = bookDAO.retrieve(book);
1025         if (logger.isDebugEnabled()) {
1026             logger.info( resourceBundleMessageSource.getMessage(
1027                     "retrieving.book.possesor.successful",
1028                     new Object[] {(new Long(book)).toString()},
1029                     Locale.US));
1030         }
1031         return bk.getOwner();
1032     }
1033 
1034     /**
1035      * Processes book request transaction. Records request(s) in transaction
1036      * table, sends email notification to possessors and confirmation email to
1037      * requester.
1038      * <p>
1039      * The algorithm works as follows:
1040      * First, we get a list of all copies of the given book whose last known location
1041      * is the given location.  If there are no such copies, BookNotFoundAtLocationException
1042      * is thrown.  If the reader has a pending request for one of these copies,
1043      * DuplicateRequestException is thrown.  If any are checked in, BookAwaitingPickupException
1044      * is thrown.
1045      * Otherwise, a request is created for each of the copies and the current possessor of each
1046      * is notified.
1047      *
1048      * @param book the id of the book being requested
1049      * @param reader the id of the reader making the request
1050      * @param location the location where the book is being requested
1051      * @exception LibraryException if an error occurs processing the
1052      * transaction
1053      * @exception DuplicateRequestException if the requester already has a
1054      * request pending for the book
1055      * @exception ReaderNotFoundException if the <code>reader</code> is not
1056      * found
1057      */
1058     public void processRequest(long reader, long book, long location)
1059             throws Exception {
1060         if (logger.isDebugEnabled()) {
1061             logger.info( resourceBundleMessageSource.getMessage(
1062                     "processing.request",
1063                     new Object[] {(new Long(reader)).toString(),
1064                             (new Long(book)).toString()},
1065                     Locale.US));
1066         }
1067 
1068         // verify that the reader and the book both exist
1069         if (!readerExists(reader)) {
1070             final String errString = resourceBundleMessageSource.getMessage(
1071                     "error.nonexistent.reader",
1072                     new Object[] {  new Long(reader) },
1073                     Locale.US);
1074             logger.error(errString);
1075             throw new ReaderNotFoundException(errString);
1076         }
1077 
1078         if (!bookExists(book)) {
1079             final String errString = resourceBundleMessageSource.getMessage(
1080                     "error.nonexistent.book",
1081                     new Object[] {  new Long(book) },
1082                     Locale.US);
1083             logger.error(errString);
1084             throw new BookNotFoundException(errString);
1085         }
1086 
1087         final String isbn = bookDAO.retrieve(book).getIsbn();
1088         // Get list of all copies of the book whose last known location is location
1089         final long[] copies = libraryTransactionDAO.getCopies(isbn, location);
1090         if (copies.length == 0) { // There is no copy of the book associated with the location
1091             // Record the request
1092             libraryTransactionDAO.processRequest(reader, book, location);
1093             Reader requestor = new Reader();
1094             Book bk = new Book();
1095             bk.setId(book);
1096             bk = bookDAO.retrieve(book);
1097             requestor.setId(reader);
1098             requestor = readerDAO.retrieve(requestor);
1099             // Notify the admin
1100             final String locationString = locationDAO.getLocationDescription(location);
1101             mailWorker.sendAcquisitionNotification(requestor, bk, locationString);
1102             // Send request confirmation
1103             mailWorker.sendAcquisitionRequestConfirmation(requestor, bk, locationString);
1104         }
1105         // Walk the list - throw BookAwaitingPickupException if any are in; otherwise
1106         // add a request transaction and send notifications for each one
1107         for (int i = 0; i < copies.length; i++) {
1108             if (requestPending(reader, copies[i])) {
1109                 final String warning = resourceBundleMessageSource.getMessage(
1110                         "error.trans.duplicate.request",
1111                         new Object[] {  (new Long(reader)).toString(),
1112                                 Long.valueOf(copies[i]).toString()},
1113                         Locale.US);
1114                 logger.warn(warning);
1115                 throw new DuplicateRequestException(warning);
1116             }
1117             final long possessorId = getPossessor(copies[i]);
1118             if (possessorId == Constants.CHECKED_IN) {
1119                 throw new BookAwaitingPickupException();
1120             }
1121             libraryTransactionDAO.processRequest(reader, copies[i], location);
1122             Reader possessor = new Reader();
1123             Reader requestor = new Reader();
1124             Book bk = new Book();
1125             bk.setId(copies[i]);
1126             bk = bookDAO.retrieve(copies[i]);
1127             possessor.setId(possessorId);
1128             possessor = readerDAO.retrieve(possessor);
1129             requestor.setId(reader);
1130             requestor = readerDAO.retrieve(requestor);
1131             mailWorker.sendRequestNotification(possessor, requestor, bk,
1132                     locationDAO.getLocationDescription(location));
1133             mailWorker.sendRequestConfirmation(possessor, requestor, bk,
1134                     locationDAO.getLocationDescription(location));
1135         }
1136         if (logger.isDebugEnabled()) {
1137             logger.info( resourceBundleMessageSource.getMessage(
1138                     "request.processed",
1139                     new Object[] {(new Long(reader)).toString(),
1140                             (new Long(book)).toString()},
1141                     Locale.US));
1142         }
1143     }
1144 
1145     /**
1146      * Gets the list of readers with outstanding requests for a copy of the book in the given location.
1147      *
1148      * @param book id of the book (used to get the ISBN to search for)
1149      * @param location id of location
1150      * @return list of requesters who have requested a copy of the book in the given location
1151      * @throws LibraryException
1152      */
1153     private ArrayList<Reader> getRequestors(long book, long location)
1154             throws LibraryException {
1155         if (logger.isDebugEnabled()) {
1156             logger.info( resourceBundleMessageSource.getMessage(
1157                     "retrieving.requesters",
1158                     new Object[] {Long.toString(book), Long.toBinaryString(location)},
1159                     Locale.US));
1160         }
1161 
1162         final Book bk = retrieveBook(book);
1163         final List<Long> readerIdList = libraryTransactionDAO.getRequestors(bk.getIsbn(), location);
1164         final ArrayList<Reader> outList = new ArrayList<Reader>();
1165         final Iterator<Long> it = readerIdList.iterator();
1166 
1167         while(it.hasNext()) {
1168             final Long readerId = it.next();
1169             Reader rd = new Reader();
1170             rd.setId(readerId);
1171             rd = readerDAO.retrieve(rd);
1172             if ( rd != null ) {
1173                 outList.add(rd);
1174             }
1175         }
1176 
1177         if (logger.isDebugEnabled()) {
1178             logger.info( resourceBundleMessageSource.getMessage(
1179                     "requesters.retrieved",
1180                     new Object[] {Long.toString(book), Long.toString(location), Integer.toString(outList.size())},
1181                     Locale.US));
1182         }
1183 
1184         return outList;
1185     }
1186 
1187 
1188     /**
1189      * Determines whether or not a reader has a request pending for a book.
1190      *
1191      * @param reader the ID of the reader
1192      * @param book the ID of the book
1193      * @return true if the reader with ID <code>reader</code> has a request
1194      * pending for the book with ID <code>book</code>; false otherwise
1195      * @throws LibraryException if a data access error occurs
1196      */
1197     public boolean requestPending(long reader,long book)
1198             throws LibraryException {
1199         if (logger.isDebugEnabled()) {
1200             logger.debug(
1201                     "reader=" + reader+ ", book=" + book);
1202         }
1203 
1204         return libraryTransactionDAO.requestPending( reader, book );
1205     }
1206 
1207     /**
1208      * Cancels a pending request for a book and sends confirmation email to
1209      * the reader canceling the request.
1210      *
1211      * @param reader the ID of the reader canceling the request
1212      * @param book the ID of the book
1213      * @throws LibraryException if the reader or book does not exist, or if
1214      * there is an error canceling the request
1215      */
1216     public void cancelRequest(long reader, long book)
1217             throws LibraryException {
1218         if (logger.isDebugEnabled()) {
1219             logger.info( resourceBundleMessageSource.getMessage(
1220                     "cancelling.request",
1221                     new Object[] {(new Long(reader)).toString(),
1222                             (new Long(book)).toString()},
1223                     Locale.US));
1224         }
1225 
1226         // verify that the reader and the book both exist
1227         if (!readerDAO.readerExists(reader)) {
1228             final String errString = resourceBundleMessageSource.getMessage(
1229                     "error.nonexistent.reader",
1230                     new Object[] {  new Long(reader) },
1231                     Locale.US);
1232             logger.error(errString);
1233             throw new ReaderNotFoundException(errString);
1234         }
1235 
1236         if (!bookExists(book)) {
1237             final String errString = resourceBundleMessageSource.getMessage(
1238                     "error.nonexistent.book",
1239                     new Object[] {  new Long(book) },
1240                     Locale.US);
1241             logger.error(errString);
1242             throw new BookNotFoundException(errString);
1243         }
1244 
1245         libraryTransactionDAO.cancelRequest(reader, retrieveBook(book).getIsbn());
1246 
1247         // send confirmation email
1248         final Book bk = bookDAO.retrieve(book);
1249         Reader rd = new Reader();
1250         rd.setId(reader);
1251         rd = readerDAO.retrieve(rd);
1252         try {
1253             mailWorker.sendDeleteConfirmation(rd,bk);
1254         } catch( final Exception ex) {
1255             logger.error(ex);
1256         }
1257 
1258         if (logger.isDebugEnabled()) {
1259             logger.info( resourceBundleMessageSource.getMessage(
1260                     "request.cancelled",
1261                     new Object[] {(new Long(reader)).toString(),
1262                             (new Long(book)).toString()},
1263                     Locale.US));
1264         }
1265     }
1266 
1267     /**
1268      * Gets the entire transaction history for a book and returns
1269      * an ArrayList of {@link LibraryTransaction} instances.
1270      *
1271      * @param book id of the book to retrieve transactions for
1272      * @throws LibraryException if a data access error occurs
1273      */
1274     public List<LibraryTransaction> getTransactions(long book)
1275             throws LibraryException {
1276         if (logger.isDebugEnabled()) {
1277             logger.info( resourceBundleMessageSource.getMessage(
1278                     "retrieving.transactions",
1279                     new Object[] {(new Long(book)).toString()},
1280                     Locale.US));
1281         }
1282 
1283         if (!bookExists(book)) {
1284             final String errString = resourceBundleMessageSource.getMessage(
1285                     "error.nonexistent.book",
1286                     new Object[] {new Long(book)},
1287                     Locale.US);
1288             logger.error(errString);
1289             throw new BookNotFoundException(errString);
1290         }
1291 
1292         // Create dummy "add" transaction
1293         final Book bk = bookDAO.retrieve(book);
1294         final LibraryTransaction trans = new LibraryTransaction();
1295         trans.setBook(book);
1296         trans.setBookTitle(bk.getTitle());
1297         final long reader = bk.getOwner();
1298         trans.setReader(reader);
1299         Reader rd = new Reader();
1300         rd.setId(reader);
1301         rd = readerDAO.retrieve(rd);
1302         if (rd != null) {
1303             trans.setReaderName(rd.getFirstName() + " " + rd.getLastName());
1304             trans.setReaderPhone(rd.getDeskPhone());
1305             trans.setTimeString(bk.getCreated());
1306             trans.setAction(Constants.ADD_ACTION);
1307             final String action = resourceBundleMessageSource.getMessage(
1308                     "trans.add.label",
1309                     null,
1310                     Locale.US);
1311             trans.setActionString( action );
1312         }
1313 
1314         // Create output list, starting with dummy add transaction
1315         final ArrayList<LibraryTransaction> result = new ArrayList<LibraryTransaction>();
1316         result.add(trans);
1317 
1318         // Get actual transactions for the book and append to output list
1319         result.addAll(libraryTransactionDAO.getTransactions(book));
1320 
1321         if (logger.isDebugEnabled()) {
1322             logger.info( resourceBundleMessageSource.getMessage(
1323                     "transactions.retrieved",
1324                     new Object[] {(new Long(book)).toString()},
1325                     Locale.US));
1326         }
1327 
1328         return result;
1329     }
1330 
1331     /**
1332      * Resets a user's password.
1333      *
1334      * <p>Fails silently if the user does not exist.
1335      * </p>
1336      * @param user UID of the user
1337      * @param newPwd new password
1338      * @exception LibraryException if an error occurs resetting the password
1339      */
1340     public void resetPassword(String user, String newPwd)
1341             throws LibraryException {
1342         if (logger.isDebugEnabled()) {
1343             logger.debug(
1344                     "user=" + user + ", newPwd=" + newPwd);
1345         }
1346 
1347         readerDAO.resetPassword(user, newPwd);
1348     }
1349 
1350     /**
1351      * Validates that <code>uid</code> exists and has <code>email</code> as
1352      * email address of record, then resets the associated password to a
1353      * randomly generated string and sends the reader an email with the new
1354      * password.
1355      *
1356      * @param uid  UID to reset password for
1357      * @param email = email address - for confirmation and message target
1358      * @throws LibraryException if the <code>uid</code> is not found or if an
1359      * error occurs resetting the password or sending the notification
1360      */
1361     public void forgotPassword(String uid, String email)
1362             throws LibraryException {
1363         if (logger.isDebugEnabled()) {
1364             logger.info( resourceBundleMessageSource.getMessage(
1365                     "forgotPassword.processing",
1366                     new Object[] { uid, email },
1367                     Locale.US));
1368         }
1369 
1370         final Reader rd = retrieveByUid(uid);
1371 
1372         if (rd == null || !rd.getEmail().equalsIgnoreCase(email) ) {
1373             final String errString = resourceBundleMessageSource.getMessage(
1374                     "error.nonexistent.reader",
1375                     new Object[] {  uid + ", " + email },
1376                     Locale.US);
1377             logger.error(errString);
1378             throw new LibraryException(errString);
1379         }
1380 
1381         // Randomly generate a new password
1382         final byte[] bytes = new byte[8];
1383         new java.util.Random().nextBytes( bytes );
1384         final String newPassword = crypto.convert( bytes );
1385 
1386         try {
1387             // Reset the password
1388             resetPassword(uid, newPassword);
1389 
1390             // Send email with the new password
1391             mailWorker.sendNewPasswordConfirmation( rd, newPassword );
1392         } catch( final Exception ex) {
1393             final String errString = resourceBundleMessageSource.getMessage(
1394                     "error.trans.request.badrequest",
1395                     new Object[] {  uid, email },
1396                     Locale.US);
1397             logger.error(errString);
1398             throw new LibraryException(errString);
1399         }
1400 
1401         if (logger.isDebugEnabled()) {
1402             logger.info( resourceBundleMessageSource.getMessage(
1403                     "forgotPassword.processed.successfully",
1404                     new Object[] {uid, email},
1405                     Locale.US));
1406         }
1407     }
1408 
1409     /**
1410      * Looks up the book indicated by the ISBN with SRU/SRW provider, returning
1411      * a Map of key-value pairs expressing attributes of the book.  The map
1412      * has the following keys:
1413      * <pre>
1414      *         Constants.BOOKTITLE
1415      *         Constants.BOOKSUBJECTSLIST
1416      *         Constants.BOOKAUTHOR
1417      *         Constants.BOOKPUBLISHER
1418      *         Constants.BOOKDATE
1419      * </pre>
1420      * All keys are mapped to String values, except Constants.BOOKSUBJECTSLIST
1421      * which maps to a List of Strings.
1422      *
1423      * @param isbn  ISBN of the book
1424      * @return Map of key-value pairs describing the book
1425      * @throws LibraryException if the <code>isbn</code> is not found or if an error occurs
1426      */
1427     public Map lookupBook(String isbn)
1428             throws LibraryException {
1429         if (logger.isDebugEnabled()) {
1430             logger.debug(
1431                     "isbn=" + isbn);
1432         }
1433 
1434         try {
1435             return sruClient.callSruSrwProvider(isbn);
1436         } catch(final Throwable ex) {
1437             if (logger.isDebugEnabled()) {
1438                 logger.debug( ex.getCause() );
1439             }
1440 
1441             throw new LibraryException(ex.getMessage(), ex);
1442         }
1443     }
1444 }