View Javadoc

1   package org.sourceforge.vlibrary.user.dao;
2   
3   
4   import java.sql.Types;
5   import java.util.ArrayList;
6   import java.util.Iterator;
7   import java.util.List;
8   import java.util.Locale;
9   import java.util.Map;
10  
11  import org.apache.commons.lang.StringUtils;
12  import org.apache.log4j.Logger;
13  import org.sourceforge.vlibrary.Constants;
14  import org.sourceforge.vlibrary.exceptions.LibraryException;
15  import org.sourceforge.vlibrary.user.domain.Author;
16  import org.sourceforge.vlibrary.user.domain.Book;
17  import org.sourceforge.vlibrary.user.domain.Subject;
18  import org.sourceforge.vlibrary.util.Utils;
19  import org.springframework.context.support.ResourceBundleMessageSource;
20  import org.springframework.jdbc.core.JdbcTemplate;
21  
22  
23  /**
24   * Book DAO.
25   *
26   * @version $Revision$ $Date$
27   */
28  
29  public class BookDAO {
30  
31      /** log4j Logger */
32      private static Logger logger = Logger.getLogger(
33              BookDAO.class.getName());
34  
35      private ResourceBundleMessageSource resourceBundleMessageSource;
36  
37      private JdbcTemplate jdbcTemplate;
38      private String insertBookSQL;
39      private String selectBookByISBNSQL;
40      private String countCopiesSQL;
41      private String selectBookByIdSQL;
42      private String updateBookSQL;
43      private String selectBookAuthorByBookIdAuthoridSQL;
44      private String insertBookAuthorSQL;
45      private String selectAuthorsByBookIdSQL;
46      private String selectSubjectBySubjectIdBookIdSQL;
47      private String insertSubjectBookSQL;
48      private String selectSubjectsByBookIdSQL;
49      private String selectAllBooksSQL;
50      private String selectBookByTitleSQL;
51      private String selectBookByOwnerSQL;
52      private String selectBookByAuthorSQL;
53      private String selectSubjectOrSearchMainSQL;
54      private String selectSubjectOrSearchOrderBySQL;
55      private String selectSubjectAndSearchSelectSQL;
56      private String selectSubjectAndSearchWhereSQL;
57      private String selectSubjectAndSearchOrderBySQL;
58      private AuthorDAO authorDAO;
59      private SubjectDAO subjectDAO;
60  
61      /**
62       * Create a BookDAO.
63       */
64      public BookDAO() {
65      }
66  
67      /**
68       * Used for Spring Dependency Injection
69       * @param jdbcTemplate The jdbcTemplate to set.
70       */
71      public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
72          this.jdbcTemplate = jdbcTemplate;
73      }
74  
75      /**
76       * Used for Spring Dependency Injection
77       */
78      public void setResourceBundleMessageSource(
79              ResourceBundleMessageSource resourceBundleMessageSource) {
80          this.resourceBundleMessageSource = resourceBundleMessageSource;
81      }
82  
83      /**
84       * Used for Spring Dependency Injection
85       */
86      public void setSelectSubjectAndSearchSelectSQL(
87              String selectSubjectAndSearchSelectSQL) {
88          this.selectSubjectAndSearchSelectSQL = selectSubjectAndSearchSelectSQL;
89      }
90  
91      /**
92       * Used for Spring Dependency Injection
93       */
94      public void setSelectSubjectAndSearchWhereSQL(
95              String selectSubjectAndSearchWhereSQL) {
96          this.selectSubjectAndSearchWhereSQL = selectSubjectAndSearchWhereSQL;
97      }
98  
99      /**
100      * Used for Spring Dependency Injection
101      */
102     public void setSelectSubjectAndSearchOrderBySQL(
103             String selectSubjectAndSearchOrderBySQL) {
104         this.selectSubjectAndSearchOrderBySQL =
105                 selectSubjectAndSearchOrderBySQL;
106     }
107 
108     /**
109      * Used for Spring Dependency Injection
110      */
111     public void setInsertBookSQL( String insertBookSQL) {
112         this.insertBookSQL = insertBookSQL;
113     }
114 
115     /**
116      * Used for Spring Dependency Injection
117      */
118     public void setSelectSubjectOrSearchMainSQL(
119             String selectSubjectOrSearchMainSQL) {
120         this.selectSubjectOrSearchMainSQL = selectSubjectOrSearchMainSQL;
121     }
122 
123     /**
124      * Used for Spring Dependency Injection
125      */
126     public void setSelectSubjectOrSearchOrderBySQL(
127             String selectSubjectOrSearchOrderBySQL) {
128         this.selectSubjectOrSearchOrderBySQL = selectSubjectOrSearchOrderBySQL;
129     }
130 
131     /**
132      * Used for Spring Dependency Injection
133      */
134     public void setSelectBookByISBNSQL( String selectBookByISBNSQL) {
135         this.selectBookByISBNSQL = selectBookByISBNSQL;
136     }
137 
138     /**
139      * Used for Spring Dependency Injection
140      */
141     public void setCountCopiesSQL( String countCopiesSQL) {
142         this.countCopiesSQL = countCopiesSQL;
143     }
144 
145     /**
146      * Used for Spring Dependency Injection
147      */
148     public void setSelectBookByIdSQL( String selectBookByIdSQL) {
149         this.selectBookByIdSQL = selectBookByIdSQL;
150     }
151 
152     /**
153      * Used for Spring Dependency Injection
154      */
155     public void setUpdateBookSQL( String updateBookSQL) {
156         this.updateBookSQL = updateBookSQL;
157     }
158 
159     /**
160      * Used for Spring Dependency Injection
161      */
162     public void setSelectBookAuthorByBookIdAuthoridSQL(
163             String selectBookAuthorByBookIdAuthoridSQL) {
164         this.selectBookAuthorByBookIdAuthoridSQL =
165                 selectBookAuthorByBookIdAuthoridSQL;
166     }
167 
168     /**
169      * Used for Spring Dependency Injection
170      */
171     public void setInsertBookAuthorSQL( String insertBookAuthorSQL) {
172         this.insertBookAuthorSQL = insertBookAuthorSQL;
173     }
174 
175     /**
176      * Used for Spring Dependency Injection
177      */
178     public void setSelectAuthorsByBookIdSQL(String selectAuthorsByBookIdSQL) {
179         this.selectAuthorsByBookIdSQL = selectAuthorsByBookIdSQL;
180     }
181 
182     /**
183      * Used for Spring Dependency Injection
184      */
185     public void setSelectSubjectBySubjectIdBookIdSQL(
186             String selectSubjectBySubjectIdBookIdSQL) {
187         this.selectSubjectBySubjectIdBookIdSQL =
188                 selectSubjectBySubjectIdBookIdSQL;
189     }
190 
191     /**
192      * Used for Spring Dependency Injection
193      */
194     public void setInsertSubjectBookSQL( String insertSubjectBookSQL) {
195         this.insertSubjectBookSQL = insertSubjectBookSQL;
196     }
197 
198     /**
199      * Used for Spring Dependency Injection
200      */
201     public void setSelectSubjectsByBookIdSQL(
202             String selectSubjectsByBookIdSQL) {
203         this.selectSubjectsByBookIdSQL = selectSubjectsByBookIdSQL;
204     }
205 
206     /**
207      * Used for Spring Dependency Injection
208      */
209     public void setSelectAllBooksSQL( String selectAllBooksSQL) {
210         this.selectAllBooksSQL = selectAllBooksSQL;
211     }
212 
213     /**
214      * Used for Spring Dependency Injection
215      */
216     public void setSelectBookByTitleSQL( String selectBookByTitleSQL) {
217         this.selectBookByTitleSQL = selectBookByTitleSQL;
218     }
219 
220     /**
221      * Used for Spring Dependency Injection
222      */
223     public void setSelectBookByOwnerSQL( String selectBookByOwnerSQL) {
224         this.selectBookByOwnerSQL = selectBookByOwnerSQL;
225     }
226 
227     /**
228      * Used for Spring Dependency Injection
229      */
230     public void setSelectBookByAuthorSQL( String selectBookByAuthorSQL) {
231         this.selectBookByAuthorSQL = selectBookByAuthorSQL;
232     }
233 
234     /**
235      * Used for Spring Dependency Injection
236      */
237     public void setAuthorDAO(AuthorDAO authorDAO) {
238         this.authorDAO = authorDAO;
239     }
240 
241     /**
242      * Used for Spring Dependency Injection
243      */
244     public void setSubjectDAO(SubjectDAO subjectDAO) {
245         this.subjectDAO = subjectDAO;
246     }
247 
248     /**
249      * <p>Inserts a book into the database and returns the result of retrieving
250      * the inserted book. The returned book instance therefore has the ID of
251      * the newly generated record in the database. The ID field in the book
252      * passed in as actual parameter is ignored.
253      * </p>
254      * <p>The supplied book must have minimally a title and isbn, otherwise
255      * a LibraryException is thrown.
256      * </p>
257      * <p>NB: this method does NOT set subjects or authors</p>
258      *
259      * @throws LibraryException if an error occurs
260      */
261     public Book insert(Book book) throws LibraryException {
262         if (book == null || book.getTitle() == null ) {
263             final String message = resourceBundleMessageSource.getMessage(
264                     "error.book.insert.missing.data",
265                     new Object[] {book==null?"":book.toString()},
266                     Locale.US);
267             logger.error(message);
268             throw new LibraryException(message);
269         }
270 
271         if (logger.isDebugEnabled()) {
272             logger.debug(resourceBundleMessageSource.getMessage(
273                     "inserting.book",
274                     new Object[] {book.toString()},
275                     Locale.US));
276         }
277 
278         try {
279             jdbcTemplate.update(insertBookSQL,
280                     new Object [] {
281                             StringUtils.replace
282                             (book.getTitle(),Constants.APOSTROPHE,
283                                     Constants.ESCAPED_APOSTROPHE)
284                             ,
285                             StringUtils.replace
286                             (book.getPublisher(),Constants.APOSTROPHE,
287                                     Constants.ESCAPED_APOSTROPHE)
288                             ,
289                             StringUtils.replace
290                             (book.getPub_date(),Constants.APOSTROPHE,
291                                     Constants.ESCAPED_APOSTROPHE)
292                             ,
293                             new Long(book.getOwner())
294                             ,
295                             book.normalizeIsbn(book.getIsbn())
296             },
297                     new int[] {Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
298                             Types.INTEGER, Types.VARCHAR}
299                     );
300         } catch (final Exception se) {
301             final String errString = resourceBundleMessageSource.getMessage(
302                     "error.book.insert.sql",
303                     new Object[] { book.toString() },
304                     Locale.US);
305             logger.error(errString,se);
306             throw new LibraryException(errString,se);
307         }
308 
309         //FIXME: this is bogus - should use getGeneratedKeys from statement itself to be sure
310         book = retrieveByIsbn(book.normalizeIsbn(book.getIsbn()));
311 
312         // Count the number of books with this ISBN.
313         // If there are others, make this one count - 1.
314         final int count = countCopies(book.getIsbn());
315         if (count > 1) {
316             book.setCopy(count - 1);
317             update(book);
318         }
319 
320         if (logger.isDebugEnabled()) {
321             logger.debug( resourceBundleMessageSource.getMessage(
322                     "book.insert.successful",
323                     new Object[] { book.toString() },
324                     Locale.US));
325         }
326 
327         return book;
328     }
329 
330     /**
331      *  <p>Updates <code>book</code> in the database. Assumes that all required
332      *  fields are set, including <code>id</code>, which is used to locate the
333      *  record to update.
334      *  </p>
335      *
336      *  <p>NB: this method does NOT set subjects or authors.</p>
337      *
338      * @throws LibraryException
339      */
340     public void update(Book book) throws LibraryException {
341         if (book == null || book.getTitle() == null ) {
342             final String message = resourceBundleMessageSource.getMessage(
343                     "error.book.update.missing.data",
344                     new Object[] {book==null?"":book.toString()},
345                     Locale.US);
346             logger.error(message);
347             throw new LibraryException(message);
348         }
349 
350         if (logger.isDebugEnabled()) {
351             logger.debug( resourceBundleMessageSource.getMessage(
352                     "updating.book",
353                     new Object[] { book.toString() },
354                     Locale.US));
355         }
356 
357         try {
358             jdbcTemplate.update(updateBookSQL,
359                     new Object [] {
360                             StringUtils.replace
361                             (book.getTitle(),Constants.APOSTROPHE,
362                                     Constants.ESCAPED_APOSTROPHE)
363                             ,
364                             StringUtils.replace
365                             (book.getPublisher(),Constants.APOSTROPHE,
366                                     Constants.ESCAPED_APOSTROPHE)
367                             ,
368                             StringUtils.replace
369                             (book.getPub_date(),Constants.APOSTROPHE,
370                                     Constants.ESCAPED_APOSTROPHE)
371                             ,
372                             new Long(book.getOwner())
373                             ,
374                             book.normalizeIsbn(book.getIsbn())
375                             ,
376                             new Integer(book.getCopy())
377                             ,
378                             new Long(book.getId())
379             },
380                     new int[] {Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
381                             Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.INTEGER}
382                     );
383 
384         } catch (final Exception se) {
385             final String errString = resourceBundleMessageSource.getMessage(
386                     "error.book.update",
387                     new Object[] { book.toString() },
388                     Locale.US);
389             logger.error(errString,se);
390             throw new LibraryException(errString,se);
391         }
392 
393         if (logger.isDebugEnabled()) {
394             logger.debug( resourceBundleMessageSource.getMessage(
395                     "book.update.successful",
396                     new Object[] { book.toString() },
397                     Locale.US));
398         }
399     }
400 
401     /**
402      * Retrieve Book by unique criteria from the database.  If there
403      * is more than one book satisfying the query, the first record
404      * in the resultset is used to populate the book record.
405      *
406      * @param sqlString SQL String to be used for the specific criteria Object
407      * @param criteria Unique criteria to search the reader from, such as id,
408      * isbn
409      * @return Book Fully populated Book object, or NULL if a record
410      * matching criteria could not be located in the DB
411      * @throws LibraryException
412      */
413     public Book retrieveByGenericCriteria(String sqlString,
414             Object[] criteria) throws LibraryException {
415         if (logger.isDebugEnabled()) {
416             logger.debug( resourceBundleMessageSource.getMessage(
417                     "retrieving.book",
418                     criteria,
419                     Locale.US));
420         }
421 
422         Book book = null;
423 
424         try {
425             final List rows = jdbcTemplate.queryForList(sqlString, criteria);
426             book = loadBookProperties(rows);
427         } catch (final Exception se) {
428             final String errString = resourceBundleMessageSource.getMessage(
429                     "error.book.retrieve",
430                     criteria,
431                     Locale.US);
432             logger.error(errString,se);
433             throw new LibraryException(errString,se);
434         }
435 
436         if (logger.isDebugEnabled()) {
437             logger.debug(resourceBundleMessageSource.getMessage(
438                     "book.retrieve.successful",
439                     new Object[] {book==null?null:book.toString() },
440                     Locale.US));
441         }
442 
443         return book;
444     }
445 
446     /**
447      * Populates Book from for first record in rs.
448      *
449      * @param rs List containing DB records (each record is a Map)
450      * @return Book Populated Book object
451      * @throws LibraryException - If there is no record returned from DB
452      */
453     private Book loadBookProperties(List rs)
454             throws LibraryException {
455         if (rs == null) {
456             final String errString = resourceBundleMessageSource.getMessage(
457                     "error.book.retrieve",
458                     new Object[] { rs },
459                     Locale.US);
460             logger.error(errString);
461             throw new LibraryException(errString);
462         }
463 
464         Book book = null;
465         final Iterator it = rs.iterator();
466         if (it.hasNext()) {
467             final Map record = (Map)it.next();
468             book = loadBookPropertiesfromRecord(record);
469         }
470 
471         return book;
472     }
473 
474     /**
475      * Populates Book from DB record
476      *
477      * @param List List containing DB records (each record is a Map)
478      * @return Book Populated Book object
479      * @throws LibraryException - If there is no record returned from DB
480      */
481     private Book loadBookPropertiesfromRecord(Map<String, Object> record)
482             throws LibraryException {
483         final Book book = new Book();
484         book.setId(new Long(((Integer) Utils.getIgnoreCase(
485                 record, "ID")).longValue()));
486         book.setCopy(((Integer) Utils.getIgnoreCase(record, "COPY")).intValue());
487         book.setTitle((String) Utils.getIgnoreCase(record, "TITLE"));
488         book.setPublisher((String) Utils.getIgnoreCase(record, "PUBLISHER"));
489         book.setIsbn((String) Utils.getIgnoreCase(record, "ISBN"));
490         book.setPub_date((String) Utils.getIgnoreCase(record, "PUB_DATE"));
491         book.setOwner(new Long(((Integer) Utils.getIgnoreCase(
492                 record, "OWNER")).longValue()));
493 
494         return book;
495     }
496 
497     /**
498      * Gets the ISBN of the book with the given ID if there is such a book,
499      * null otherwise.
500      *
501      * @param bookID ID of book
502      * @return ISBN of the book with the given ID
503      * @throws LibraryException if an error occurs searching for the book
504      */
505     public String getIsbn(long bookID) throws LibraryException {
506         final Book book = retrieve(bookID);
507         if (null == book) {
508             return null;
509         } else {
510             return retrieve(bookID).getIsbn();
511         }
512     }
513 
514     /**
515      * Retrieves the book with the given ID, if there is such a book;
516      * otherwise Null.
517      *
518      * @param bookID ID to lookup
519      * @return book with the given ID
520      * @throws LibraryException
521      */
522     public Book retrieve(long bookID) throws LibraryException {
523         return retrieve(Long.toString( bookID));
524     }
525 
526     /**
527      * Retrieves the book having the ID represented by the input string, if
528      * there is such a book; otherwise Null.
529      *
530      * @param bookID string ID
531      * @return book with the given ID
532      * @throws LibraryException
533      */
534     public Book retrieve(String bookID) throws LibraryException {
535         return retrieveByGenericCriteria(selectBookByIdSQL,
536                 new Object[] { bookID } );
537     }
538 
539     /**
540      * Retrieves the book having the given ISBN,
541      * there is such a book; otherwise Null.
542      *
543      * @param bookID string ID
544      * @return book with the given ID
545      * @throws LibraryException
546      */
547     public Book retrieveByIsbn( String isbn ) throws LibraryException {
548         return retrieveByGenericCriteria(selectBookByISBNSQL,
549                 new Object[] {isbn});
550     }
551 
552     /**
553      * Counts the number of copies of the book, checked in or out, any location.
554      *
555      * @param isbn ISBN of the book
556      * @return the total number of copies that have been added of the given book.
557      * @throws LibraryException
558      */
559     public int countCopies(String isbn) throws LibraryException {
560         int count;
561         final Object[] criteria = new Object[] {isbn};
562         if (logger.isDebugEnabled()) {
563             logger.debug(resourceBundleMessageSource.getMessage(
564                     "counting.book.copies",
565                     criteria,
566                     Locale.US));
567         }
568         try {
569             count = jdbcTemplate.queryForInt(countCopiesSQL, criteria);
570         } catch (final Exception se) {
571             final String errString = resourceBundleMessageSource.getMessage(
572                     "error.book.counting",
573                     criteria,
574                     Locale.US);
575             logger.error(errString,se);
576             throw new LibraryException(errString,se);
577         }
578 
579         if (logger.isDebugEnabled()) {
580             logger.debug(resourceBundleMessageSource.getMessage(
581                     "book.count.successful",
582                     new Object[] {Integer.valueOf(count)},
583                     Locale.US));
584         }
585         return count;
586     }
587 
588     /**
589      *  Adds <code>author</code> to the authors of <code>book</code>.
590      *  Will create a record for <code>author</code> if no author with
591      *  the given name exists yet. Requires that <code>book</code> exist
592      *  already in the database and that the book ID is set.
593      *
594      * @param author Author to add
595      * @param book book to associate author with
596      * @throws LibraryException
597      */
598     public void addAuthor(Book book, Author author) throws
599     LibraryException {
600         if (logger.isDebugEnabled()) {
601             logger.debug( resourceBundleMessageSource.getMessage(
602                     "inserting.book.author",
603                     new Object[] {
604                             new Long(author.getId()),
605                             new Long(book.getId())
606                     }
607                     ,
608                     Locale.US));
609         }
610 
611         authorDAO.insert( author ); // will not add record if already there
612 
613         try {
614             // See if record already exists
615             List rows;
616 
617             rows = jdbcTemplate.queryForList(
618                     selectBookAuthorByBookIdAuthoridSQL,
619                     new Object[] {
620                             new Long(author.getId()),
621                             new Long(book.getId())
622                     }
623                     );
624 
625             final Iterator it = rows.iterator();
626 
627             if ( it.hasNext()) {
628                 return;
629             }
630 
631             // make sure Author exists
632             if (authorDAO.findAuthorByFirstLastName(author) == null) {
633                 authorDAO.insert(author);
634             }
635 
636             jdbcTemplate.update(    insertBookAuthorSQL,
637                     new Object [] {
638                             new Long(author.getId()),
639                             new Long(book.getId())
640             },
641                     new int[] {Types.INTEGER, Types.INTEGER}
642                     );
643         } catch (final Exception se) {
644             final String errString = resourceBundleMessageSource.getMessage(
645                     "error.inserting.book.author",
646                     new Object[] {
647                             new Long(author.getId()),
648                             new Long(book.getId())
649                     },
650                     Locale.US);
651             logger.error(errString,se);
652             throw new LibraryException(errString,se);
653         }
654 
655         if (logger.isDebugEnabled()) {
656             logger.debug( resourceBundleMessageSource.getMessage(
657                     "inserting.book.author.successful",
658                     new Object[] {
659                             new Long(author.getId()),
660                             new Long(book.getId())
661                     }
662                     ,
663                     Locale.US));
664         }
665     }
666 
667     /**
668      * Returns an ArrayList consisting of the authors of the given book.
669      * Assumes that book.id is set. Returns an empty list if there are no
670      * authors associated with the given book.
671      *
672      * @throws LibraryException
673      */
674     public ArrayList<Author> getAuthors(Book book) throws LibraryException {
675         if (book == null ||  book.getTitle() == null ) {
676             final String message = resourceBundleMessageSource.getMessage(
677                     "error.book.retrieve.missing.data",
678                     new Object[] { book==null?"":book.toString() },
679                     Locale.US);
680             logger.error(message);
681             throw new LibraryException( message );
682         }
683 
684         if (logger.isDebugEnabled()) {
685             logger.debug( resourceBundleMessageSource.getMessage(
686                     "retrieving.book.authors",
687                     new Object[] { book.toString() },
688                     Locale.US));
689         }
690 
691         final ArrayList<Author> outList = new ArrayList<Author>();
692 
693         try {
694             List rows;
695 
696             rows = jdbcTemplate.queryForList(selectAuthorsByBookIdSQL,
697                     new Object[] {new Long(book.getId()) }
698                     );
699 
700             final Iterator<Map<String, Object>> it = rows.iterator();
701 
702             while(it.hasNext()) {
703                 final Map<String, Object> record = it.next();
704                 final Author au = new Author(
705                         (String) Utils.getIgnoreCase(record, "FIRSTNAME"),
706                         (String) Utils.getIgnoreCase(record, "LASTNAME"));
707                 au.setId(new Long(((Integer) Utils.getIgnoreCase(
708                         record, "ID")).longValue()));
709                 outList.add(au);
710             }
711 
712         } catch (final Exception se) {
713             final String errString = resourceBundleMessageSource.getMessage(
714                     "error.book.retrieve.sql",
715                     new Object[] { book.toString() },
716                     Locale.US);
717             logger.error(errString,se);
718             throw new LibraryException(errString,se);
719         }
720 
721         if (logger.isDebugEnabled()) {
722             logger.debug( resourceBundleMessageSource.getMessage(
723                     "book.author.retrieve.successful",
724                     new Object[] { outList },
725                     Locale.US));
726         }
727 
728         return outList;
729     }
730 
731     /**
732      *  Adds <code>subject</code> to the subjects associated with
733      *  <code>book</code>. Will create a record for <code>subject</code> if no
734      *  subject with the given description exists yet. Requires that
735      *  <code>book</code> exist already in the database and that the book ID
736      *  is set.
737      *
738      * @param subject Subject to add
739      * @param book book to associate subject with
740      * @throws LibraryException
741      */
742     public void addSubject(Book book, Subject subject) throws LibraryException {
743         if (logger.isDebugEnabled()) {
744             logger.debug( resourceBundleMessageSource.getMessage(
745                     "updating.book.subjects",
746                     new Object[] { book.toString() + subject.toString()},
747                     Locale.US));
748         }
749 
750         if (book.getId() > 0) {
751             //See if record already exists
752             try {
753                 List rows;
754 
755                 rows = jdbcTemplate.queryForList(
756                         selectSubjectBySubjectIdBookIdSQL,
757                         new Object[] {
758                                 new Long(subject.getId()),
759                                 new Long(book.getId())
760                         }
761                         );
762 
763                 final Iterator it = rows.iterator();
764 
765                 if (it.hasNext()) {
766                     return;
767                 }
768             } catch (final Exception se) {
769                 final String errString = resourceBundleMessageSource.getMessage(
770                         "error.book.retrieve.sql",
771                         new Object[] { book.toString() + subject.toString()},
772                         Locale.US);
773                 logger.error(errString,se);
774                 throw new LibraryException(errString,se);
775             }
776 
777             // Make sure that the subject exists
778             final Long id = subjectDAO.getSubjectIdByDescription(subject);
779 
780             if ( id == null ) {
781                 subject = subjectDAO.insert(subject);
782             } else {
783                 subject.setId(id.longValue());
784             }
785 
786             try {
787                 jdbcTemplate.update(insertSubjectBookSQL,
788                         new Object [] {
789                                 new Long(subject.getId()),
790                                 new Long(book.getId())
791                 },
792                         new int[] {Types.INTEGER, Types.INTEGER}
793                         );
794             } catch (final Exception se) {
795                 final String errString = resourceBundleMessageSource.getMessage(
796                         "error.book.update.sql",
797                         new Object[] { book.toString() + subject.toString()},
798                         Locale.US);
799                 logger.error(errString,se);
800                 throw new LibraryException(errString,se);
801             }
802         }
803 
804         if (logger.isDebugEnabled()) {
805             logger.debug( resourceBundleMessageSource.getMessage(
806                     "book.subjects.update.successful",
807                     new Object[] { book.toString() },
808                     Locale.US));
809         }
810     }
811 
812     /**
813      * Returns an ArrayList consisting of the subject classifications
814      * associated with the the given book. Assumes that book.id is set.
815      * Returns an empty list if there are no subjects associated with the
816      * given book.
817      *
818      * @throws LibraryException
819      */
820     public ArrayList<Subject> getSubjects(Book book) throws LibraryException {
821         if (logger.isDebugEnabled()) {
822             logger.debug( resourceBundleMessageSource.getMessage(
823                     "retrieving.book.subjects",
824                     new Object[] { book.toString() },
825                     Locale.US));
826         }
827         final ArrayList<Subject> outList = new ArrayList<Subject>();
828         try {
829             List rows;
830 
831             rows = jdbcTemplate.queryForList(
832                     selectSubjectsByBookIdSQL,
833                     new Object[] {
834                             new Long(book.getId())
835                     }
836                     );
837 
838             final Iterator<Map<String, Object>> it = rows.iterator();
839 
840             while (it.hasNext()) {
841                 final Map<String, Object> record = it.next();
842                 final Subject su = new Subject(
843                         (String) Utils.getIgnoreCase(record, "DESCRIPTION"));
844                 su.setId(((Integer) Utils.getIgnoreCase(record, "ID")).longValue());
845                 outList.add(su);
846             }
847 
848         } catch (final Exception se) {
849             final String errString = resourceBundleMessageSource.getMessage(
850                     "error.book.retrieve.sql",
851                     new Object[] { book.toString() },
852                     Locale.US);
853             logger.error(errString,se);
854             throw new LibraryException(errString,se);
855         }
856 
857         if (logger.isDebugEnabled()) {
858             logger.debug( resourceBundleMessageSource.getMessage(
859                     "book.subject.retrieve.successful",
860                     new Object[] { outList },
861                     Locale.US));
862         }
863 
864         return outList;
865     }
866 
867     /**
868      * Sets the list of authors associated with the given book. The book must
869      * exist in the database, but authors will be created if necessary. This
870      * method is equivalent to repeated application of
871      * {@link #addAuthor(Book, Author)} for each Author instance in the
872      * <code>authors</code> list.
873      *
874      * @param authors Author instances to associate with the book
875      * @param book book to set authors for
876      * @throws LibraryException
877      */
878     public void setAuthors(Book book, ArrayList<Author> authors)
879             throws LibraryException {
880         for (int i=0;i<authors.size();i++) {
881             final Author author = authors.get(i);
882             addAuthor(book,author );
883         }
884     }
885 
886     /**
887      * Sets the list of subjects associated with the given book. The book must
888      * exist in the database, but subjects will be created if necessary. This
889      * method is equivalent to repeated application of
890      * {@link #addSubject(Book, Subject)} for each Subject instance in the
891      * <code>subjects</code> list.
892      *
893      * @param subjects Subject instances to associate with the book
894      * @param book book to set subjects for
895      * @throws LibraryException
896      */
897     public void setSubjects(Book book, ArrayList<Subject> subjects)
898             throws LibraryException {
899         for( int i=0; i<subjects.size(); i++) {
900             final Subject su = subjects.get(i);
901 
902             addSubject(book, su);
903         }
904     }
905 
906     /**
907      * Returns a list of all books in the library.
908      *
909      * @return list of all books
910      * @throws LibraryException
911      */
912     public ArrayList dump()
913             throws LibraryException {
914         return executeQuery(selectAllBooksSQL, null);
915     }
916 
917     /**
918      * Returns a list of books with the given title. Returns an empty list
919      * if there are no books with the given title.
920      *
921      * @param title title to search
922      * @return list of books with the given title
923      * @throws LibraryException
924      */
925     public  ArrayList titleSearch(String title)
926             throws LibraryException {
927         final Object[] criteria = new Object[] {
928                 "%" +
929                         StringUtils.replace(title, Constants.APOSTROPHE,
930                                 Constants.ESCAPED_APOSTROPHE) +
931         "%"};
932 
933         return executeQuery(selectBookByTitleSQL, criteria);
934     }
935 
936     /**
937      * Returns a list of books owned by the reader with the given ID.  Returns
938      * an empty list if the ID belongs to a reader who does not own any books,
939      * or is not a valid ID.
940      *
941      * @param owner ID of owner
942      * @return list of books owned
943      * @throws LibraryException
944      */
945     public ArrayList ownerSearch(long owner)
946             throws LibraryException {
947         return executeQuery( selectBookByOwnerSQL,
948                 new Object[] { new Long(owner) } );
949     }
950 
951 
952     /**
953      * Returns a list of <code>Book</code> instances association with all of
954      * the subjects in the input <code>subjects</code> array. The list returned
955      * is the intersection of the books associated with each of the individual
956      * subjects. A book must be associated with every subject in the
957      * list to be included in the returned list.
958      *
959      * @param subjects list of subjects to seach on
960      * @return list of books associated with each of the subjects in the list
961      * @throws LibraryException
962      */
963     public ArrayList<Book> subjectAndSearch(ArrayList subjects)
964             throws LibraryException {
965         final StringBuffer buf = new StringBuffer(selectSubjectAndSearchSelectSQL);
966 
967         for (int i=0;i<subjects.size();i++) {
968             final Subject su = (Subject)subjects.get(i);
969 
970             buf.append(selectSubjectAndSearchWhereSQL);
971 
972             buf.append("'%");
973             buf.append(StringUtils.replace
974                     (stripEndLineChar(su.getDescription()),
975                             Constants.APOSTROPHE, Constants.ESCAPED_APOSTROPHE));
976             buf.append("%'))");
977 
978             if (i<subjects.size()-1) {
979                 buf.append(" AND ");
980             }
981         }
982 
983         buf.append( selectSubjectAndSearchOrderBySQL);
984         return executeQuery(buf.toString(), null);
985     }
986 
987     /**
988      *The strings coming from subject selection text area seem to have a
989      * CR char ('^M') at the end, this spoils the search, remove
990      */
991     private String stripEndLineChar(String input) {
992         String output = input;
993 
994         if (input.substring(input.length() -1).equalsIgnoreCase("\r" )) {
995             output = input.substring(0, input.length() -1);
996         }
997 
998         return output;
999     }
1000 
1001     /**
1002      * Returns a list of <code>Book</code> instances association with all of
1003      * the subjects in the input <code>subjects</code> array. The list returned
1004      * is the intersection of the books associated with each of the individual
1005      * subjects. A book must be associated with every subject in the
1006      * list to be included in the returned list.
1007      *
1008      * @param subjects list of subjects to seach on
1009      * @return list of books associated with each of the subjects in the list
1010      * @throws LibraryException
1011      */
1012     public ArrayList<Book> subjectOrSearch(ArrayList<Subject> subjects)
1013             throws LibraryException {
1014         // Construct "IN" clause
1015         final StringBuffer inBuff = new StringBuffer("");
1016 
1017         for (int i=0;i<subjects.size();i++) {
1018             final Subject su = subjects.get(i);
1019 
1020             inBuff.append((new Long(su.getId())).toString());
1021 
1022             if (i<subjects.size()-1) {
1023                 inBuff.append(",");
1024             }
1025         }
1026 
1027         return executeQuery(
1028                 selectSubjectOrSearchMainSQL + inBuff.toString() +
1029                 selectSubjectOrSearchOrderBySQL, null);
1030     }
1031 
1032     /**
1033      * Returns a list of <code>Book</code> instances whose author lists contain
1034      * an author with the given last name. Returns an empty list if there are
1035      * no such books.
1036      *
1037      * @param authorLastName last name of author to search
1038      * @return list of books written by authors with the given last name
1039      * @throws LibraryException
1040      */
1041     public ArrayList<Book> authorSearch(String authorLastName)
1042             throws LibraryException {
1043         final Object[] criteria = new Object[] {
1044                 StringUtils.replace(authorLastName,Constants.APOSTROPHE,
1045                         Constants.ESCAPED_APOSTROPHE)};
1046 
1047         return executeQuery(selectBookByAuthorSQL, criteria );
1048     }
1049 
1050     /**
1051      * Executes the given query with the supplied parameters and
1052      * returns a list of Book instances populated from the returned
1053      * record set.
1054      *
1055      * @param query parameterized query string
1056      * @param criteria parameters for the query
1057      * @return list of Book instances populated from the returned records
1058      * @throws LibraryException
1059      */
1060     private ArrayList<Book> executeQuery(String query, Object[] criteria )
1061             throws LibraryException {
1062         if (logger.isDebugEnabled()) {
1063             logger.debug( resourceBundleMessageSource.getMessage(
1064                     "book.search.executing",
1065                     new Object[] { query },
1066                     Locale.US));
1067         }
1068 
1069         final ArrayList<Book> outList = new ArrayList();
1070 
1071         try {
1072             List rows;
1073             if ( criteria != null ) {
1074                 rows = jdbcTemplate.queryForList(query, criteria);
1075             } else {
1076                 rows = jdbcTemplate.queryForList(query);
1077             }
1078 
1079             final Iterator<Map<String, Object>> it = rows.iterator();
1080 
1081             while (it.hasNext()) {
1082                 final Map<String, Object> record = it.next();
1083                 final Book bk = loadBookPropertiesfromRecord( record );
1084                 outList.add(bk);
1085             }
1086         } catch (final Exception se) {
1087             final String errString = resourceBundleMessageSource.getMessage(
1088                     "error.book.search.sql",
1089                     new Object[] { query },
1090                     Locale.US);
1091             logger.error(errString,se);
1092             throw new LibraryException(errString,se);
1093         }
1094 
1095         if (logger.isDebugEnabled()) {
1096             logger.debug( resourceBundleMessageSource.getMessage(
1097                     "book.search.successful",
1098                     new Object[] { outList },
1099                     Locale.US));
1100         }
1101         return outList;
1102     }
1103 
1104     /**
1105      * Determines whether or not the ID provided corresponds to a book in the
1106      * library.
1107      *
1108      * @param book ID to validate
1109      * @return true if there is a book with the given ID; false otherwise
1110      * @throws LibraryException
1111      */
1112     public boolean bookExists(long book)
1113             throws LibraryException {
1114         if (retrieve( book ) != null) {
1115             return true;
1116         } else {
1117             return false;
1118         }
1119     }
1120 }