View Javadoc

1   package org.paneris.bibliomania;
2   
3   import java.io.BufferedOutputStream;
4   import java.io.BufferedReader;
5   import java.io.File;
6   import java.io.FileOutputStream;
7   import java.io.FileReader;
8   import java.io.IOException;
9   import java.io.OutputStream;
10  import java.io.Reader;
11  import java.io.StringReader;
12  import java.net.InetAddress;
13  import java.sql.PreparedStatement;
14  import java.sql.ResultSet;
15  import java.sql.Timestamp;
16  import java.util.Enumeration;
17  import java.util.Hashtable;
18  import java.util.Locale;
19  import java.util.Properties;
20  import java.util.Vector;
21  
22  import org.melati.Melati;
23  import org.melati.MelatiConfig;
24  import org.melati.poem.AccessToken;
25  import org.melati.poem.Capability;
26  import org.melati.poem.Group;
27  import org.melati.poem.NoSuchRowPoemException;
28  import org.melati.poem.PoemTask;
29  import org.melati.poem.PoemThread;
30  import org.melati.poem.PreparedStatementFactory;
31  import org.melati.poem.Setting;
32  import org.melati.poem.TableInfo;
33  import org.melati.template.webmacro.MelatiFastWriter;
34  import org.melati.util.Email;
35  import org.melati.util.FileUtils;
36  import org.melati.util.IoUtils;
37  import org.melati.util.MelatiRuntimeException;
38  import org.melati.util.UnexpectedExceptionException;
39  import org.paneris.bibliomania.fti.FTIException;
40  import org.paneris.bibliomania.fti.IndexOther;
41  import org.paneris.bibliomania.fti.Library;
42  import org.paneris.bibliomania.fti.Text;
43  import org.paneris.bibliomania.generated.BibliomaniaDatabaseBase;
44  import org.paneris.bibliomania.metasearch.BookshopBackend;
45  import org.paneris.bibliomania.metasearch.BookshopBackendFactory;
46  import org.paneris.bibliomania.pagination.Pagination;
47  import org.webmacro.Context;
48  import org.webmacro.InitException;
49  import org.webmacro.WM;
50  import org.webmacro.WebMacro;
51  import org.webmacro.WebMacroException;
52  import org.webmacro.engine.FileTemplate;
53  import org.webmacro.engine.StreamTemplate;
54  
55  public class BibliomaniaDatabase
56    extends BibliomaniaDatabaseBase
57    implements BibliomaniaDatabaseTables {
58    private IndexOther fti = null, infoFTI = null;
59    private Properties ftiConfig;
60    private Pagination pagination = null;
61  
62    public static final int defaultDefaultSearchHitsPerText = 5,
63      defaultSearchHitsPerPage = 20,
64      defaultBookStockingsCheckIntervalDays = 7,
65      defaultBibBookBookshopSearchTimeoutSeconds = 120,
66      defaultBookStockingsCheckConcurrentMax = 2,
67      defaultBookStockingsOutputStartOffset = 25,
68      defaultBookStockingsCacheSizeMax = 10000;
69  
70    public static final String defaultPasswordReminderMessage =
71      "defaultPasswordReminderMessage.wm",
72      defaultPasswordReminderFrom = "help",
73      defaultOrderEmailFrom = "orders@bibliomania.com",
74      defaultConfirmationEmailFrom = "customercare@bibliomania.com",
75      defaultOrderEmailTo = "purchases@bibliomania.com",
76      defaultStratusEmailTo = "info@houseofstratus.com",
77      defaultContentEncoding = "ISO-8859-1",
78      defaultUploadDir = "/usr/local/share/bibliomania/books/",
79      defaultUploadURL = "/";
80    //  defaultContentEncoding = "UTF8";
81  
82    private Setting workspaceDir,
83      contentRootDir,
84      staticRootURL,
85      contentStaticRootURL,
86      cacheRootURL,
87      homepageURL,
88      orderEmailFrom,
89      confirmationEmailFrom,
90      orderEmailTo,
91      stratusEmailTo,
92      defaultSearchHitsPerText,
93      searchHitsPerPage,
94      paginationTexHeader,
95      bookStockingsCheckIntervalDays,
96      bookStockingsCheckConcurrentMax,
97      bookStockingsInBackground,
98      bibBookBookshopSearchTimeoutSeconds,
99      smtpServer,
100     passwordReminderMessage,
101     passwordReminderFrom,
102     infoFTIDir,
103     bannerURLPath,
104     bookStockingsOutputStartOffset,
105     bookStockingsCacheDir,
106     bookStockingsCacheSizeMax,
107     contentEncoding,
108     uploadDir,
109     uploadURL;
110 
111   private Currency UKCurrency;
112   //private Currency USCurrency;
113 
114   private Group registeredUserGroup;
115 
116   private Capability registeredUserCapability,
117     contentModificationCapability,
118     contentAdditionCapability;
119 
120   private User templateRegisterUser;
121 
122   private Bookshop[] bookshops = null;
123 
124   private SectionGroup readSectionGroup,
125     studySectionGroup,
126     researchSectionGroup,
127     shopSectionGroup,
128     searchSectionGroup;
129 
130   private boolean bookStockingsInBackgroundAppropriate, openAuxDBs;
131 
132   private Section drama;
133   private Author shakespeare;
134   private Book macbeth;
135   
136   public BibliomaniaDatabase() {
137     this(false);
138   }
139 
140   public BibliomaniaDatabase(boolean bookStockingsInBackgroundAppropriate) {
141     this(bookStockingsInBackgroundAppropriate, true);
142   }
143 
144   public BibliomaniaDatabase(
145     boolean bookStockingsInBackgroundAppropriate,
146     boolean openAuxDBs) {
147     this(bookStockingsInBackgroundAppropriate, openAuxDBs, null);
148   }
149 
150   public BibliomaniaDatabase(
151     boolean bookStockingsInBackgroundAppropriate,
152     boolean openAuxDBs,
153     Properties ftiConfig) {
154     this.bookStockingsInBackgroundAppropriate =
155       bookStockingsInBackgroundAppropriate;
156     this.openAuxDBs = openAuxDBs;
157     this.ftiConfig = ftiConfig;
158   }
159 
160   public boolean logSQL() {
161     return false;
162   }
163 
164   public boolean debug() {
165     return false;
166   }
167 
168   public Capability getCanAdminister() {
169     return administerCapability();
170   }
171 
172   public void connect(
173     String name,
174     String dbmsclass,
175     String url,
176     String username,
177     String password,
178     int maxConnections) {
179     super.connect(name, dbmsclass, url, username, password, maxConnections);
180 
181     inSession(AccessToken.root, new PoemTask() {
182       public void run() {
183         String root = "/usr/local/share/bibliomania";
184         ((UserTable)getUserTable()).setBoardManager(
185             ((UserTable)getUserTable()).ensure(
186                 "_manager_","FIXME","board_manager@paneris.co.uk","Bibliomania Board Manager"));
187 
188 
189         readSectionGroup =
190           getSectionGroupTable().ensure(
191             "#ff9900",
192             "read",
193             "read.gif",
194             "readg.gif",
195             "1",
196             "read",
197             "",
198             false,
199             null);
200 
201         studySectionGroup =
202           getSectionGroupTable().ensure(
203             "#9999ff",
204             "study",
205             "study.gif",
206             "studyg.gif",
207             "4",
208             "study",
209             "",
210             false,
211             null);
212 
213         researchSectionGroup =
214           getSectionGroupTable().ensure(
215             "#cc9966",
216             "research",
217             "researchb.gif",
218             "researcha.gif",
219             "6",
220             "research",
221             "",
222             false,
223             null);
224         /*
225         shopSectionGroup = getSectionGroupTable().ensure(
226             "#00cc99", "shop", "shop.gif", "shopg.gif", "3",
227             "shop", "<a href=/b/org.paneris.melati.shopping.Trolley/bibliomania/View target=main>Your Basket</a>", false, null);
228         */
229         searchSectionGroup =
230           getSectionGroupTable().ensure(
231             "#ff0000",
232             "search",
233             "search.gif",
234             "searchg.gif",
235             "2",
236             "search",
237             "",
238             true,
239             "/b/org.paneris.bibliomania.Search");
240 
241         
242         drama = getSectionTable().ensure("Drama", readSectionGroup);
243         shakespeare = getAuthorTable().ensure("William Shakespeare", drama);
244         macbeth = getBookTable().ensure("Macbeth", shakespeare, drama);
245         
246         registeredUserGroup = getGroupTable().ensure("Registered users");
247         registeredUserCapability =
248           getCapabilityTable().ensure("Access study material");
249         contentModificationCapability =
250           getCapabilityTable().ensure("Edit content database");
251         contentAdditionCapability =
252           getCapabilityTable().ensure("Add to content database");
253 
254         templateRegisterUser =
255           (User)getUserTable().getLoginColumn().firstWhereEq(
256             "_registerTemplate_");
257 
258         if (templateRegisterUser == null) {
259           templateRegisterUser = (User)getUserTable().newPersistent();
260           templateRegisterUser.setLogin("_registerTemplate_");
261           templateRegisterUser.setPassword("0.804461358580738");
262           templateRegisterUser.setEmail("none@bogus");
263           templateRegisterUser.setFulltimeeducation(false);
264           templateRegisterUser.setWantspam(false);
265           templateRegisterUser.setWantemailalerts(false);
266           templateRegisterUser.setDodgeyemail(false);
267           templateRegisterUser.setName(
268             "<Template for newly registering users>");
269           getUserTable().create(templateRegisterUser);
270         }
271 
272         workspaceDir =
273           getSettingTable().ensure(
274             "WorkspaceDir",
275             root + "/workspace",
276             "Workspace directory",
277             "Where the system should store its working files");
278 
279         infoFTIDir =
280           getSettingTable().ensure(
281             "InfoFTIDir",
282             "/infoFTI",
283             "Info-FTI directory",
284             "Where the system should store the info-page index, relative "
285               + "to WorkspaceDir");
286 
287         contentRootDir =
288           getSettingTable().ensure(
289             "ContentRootDir",
290             root + "/books",
291             "Content root directory",
292             "Where (in the local filesystem) the system should look for "
293               + "the book content files");
294 
295         staticRootURL =
296           getSettingTable().ensure(
297             "StaticRootURL",
298             "/bibliomania-static",
299             "Static root URL",
300             "Where the browser can find the Bibliomania chrome, "
301               + "relative to the server root URL");
302 
303         contentStaticRootURL =
304           getSettingTable().ensure(
305             "ContentStaticRootURL",
306             "/bibliomania-content-static",
307             "Content static root URL",
308             "Where the browser can find the Bibliomania static content, "
309               + "relative to the server root URL");
310 
311         bannerURLPath =
312           getSettingTable().ensure(
313             "BannerURLPath",
314             "/bibliomania-content-static",
315             "Banner URL Path",
316             "Where the browser can find the Bibliomania static content, "
317               + "relative to the server root URL");
318 
319         cacheRootURL =
320           getSettingTable().ensure(
321             "CacheRootURL",
322             "",
323             "Cache root URL",
324             "Where the web server should look for the banner ad, "
325               + "relative to the server root URL");
326 
327         homepageURL =
328           getSettingTable().ensure(
329             "HomepageURL",
330             "http://www.bibliomania.com",
331             "Home page URL",
332             "A full URL for the front page of bibliomania");
333 
334         defaultSearchHitsPerText =
335           getSettingTable().ensure(
336             "DefaultSearchHitsPerText",
337             defaultDefaultSearchHitsPerText,
338             "Default search hits per text",
339             "The default number of search hits to return from each text");
340 
341         searchHitsPerPage =
342           getSettingTable().ensure(
343             "SearchHitsPerPage",
344             defaultSearchHitsPerPage,
345             "Search hits per page",
346             "The number of search hits to return on each results page");
347 
348         bookStockingsCheckIntervalDays =
349           getSettingTable().ensure(
350             "StockingsCheckIntervalDays",
351             defaultBookStockingsCheckIntervalDays,
352             "Bookshop search refresh days",
353             "The number of days for which a cached bookshop search "
354               + "remains valid");
355 
356         bibBookBookshopSearchTimeoutSeconds =
357           getSettingTable().ensure(
358             "BibBookSearchTimeoutSecs",
359             defaultBibBookBookshopSearchTimeoutSeconds,
360             "Timeout for shop search for Bib book",
361             "The number of seconds which a bookshop search for a "
362               + "Bibliomania book is allowed to continue before being "
363               + "cut short");
364 
365         bookStockingsInBackground =
366           getSettingTable().ensure(
367             "StockingsCheckInBackground",
368             false,
369             "Background bookshop search",
370             "Whether to run continual bookshop searches in the background");
371 
372         bookStockingsOutputStartOffset =
373           getSettingTable().ensure(
374             "StockingsOutputStartOffset",
375             defaultBookStockingsOutputStartOffset,
376             "Start Offset for Shop DHTML",
377             "How far down the page to start DHTML output");
378 
379         bookStockingsCacheDir =
380           getSettingTable().ensure(
381             "StockingsCacheDir",
382             root + "/stockings",
383             "Book stockings cache directory",
384             "Where the system should store cached results of bookshop "
385               + "searches");
386 
387         bookStockingsCacheSizeMax =
388           getSettingTable().ensure(
389             "StockingsCacheMax",
390             defaultBookStockingsCacheSizeMax,
391             "Max cached book stockings",
392             "How many bookshop search results the system should cache");
393 
394         bookStockingsCheckConcurrentMax =
395           getSettingTable().ensure(
396             "StockingsCheckConcurrentMax",
397             defaultBookStockingsCheckConcurrentMax,
398             "Max concurrent bookshop searches",
399             "The maximum number of bookshop searches to allow "
400               + "at once (including the background search)");
401 
402         UKCurrency =
403           getCurrencyTable().ensure("UK Pound Sterling", 1d, Locale.UK);
404         //USCurrency = getCurrencyTable().ensure("US Dollar", 1.43, Locale.US);
405 
406         if (openAuxDBs) {
407           try {
408             fti = new IndexOther(new File(getWorkspaceDir()), ftiConfig);
409             infoFTI = new IndexOther(new File(getInfoFTIDir()));
410             pagination = new Pagination(new File(getWorkspaceDir()));
411           } catch (Exception e) {
412             throw new FTIException(e);
413           }
414 
415           paginationTexHeader =
416             (Setting)getSettingTable().getNameColumn().firstWhereEq(
417               "PaginationTexHeader");
418           if (paginationTexHeader == null) {
419             paginationTexHeader =
420               getSettingTable().ensure(
421                 "PaginationTexHeader",
422                 pagination.defaultTexHeader(),
423                 "TeX header for pagination",
424                 "The header to be prepended to the TeX version of a "
425                   + "chapter when TeX is run to determine page breaks");
426             paginationTexHeader.setWidth(60);
427             paginationTexHeader.setHeight(5);
428           }
429         }
430 
431         Vector bookshopsList = new Vector();
432 
433         Bookshop bol = getBookshopTable().ensure("BOLUK", "BOL UK", "uk.gif");
434         if (!Boolean.TRUE.equals(bol.getDisabled())) {
435           bol.backend = bookshopBackendNamed("bol");
436           bookshopsList.addElement(bol);
437         }
438 
439         Bookshop bob =
440           getBookshopTable().ensure(
441             "BOB",
442             "Blackwells Online Bookshop",
443             "uk.gif");
444         if (!Boolean.TRUE.equals(bob.getDisabled())) {
445           bob.backend = bookshopBackendNamed("bob");
446           bookshopsList.addElement(bob);
447         }
448 
449         Bookshop amazon =
450           getBookshopTable().ensure("AMAZON", "amazon.com", "us.gif");
451         if (!Boolean.TRUE.equals(amazon.getDisabled())) {
452           amazon.backend = bookshopBackendNamed("amazon");
453           bookshopsList.addElement(amazon);
454         }
455 
456         bookshops = new Bookshop[bookshopsList.size()];
457         bookshopsList.copyInto(bookshops);
458 
459         String defaultSmtpServer;
460         try {
461           defaultSmtpServer = InetAddress.getLocalHost().toString();
462         } catch (Exception e) {
463           defaultSmtpServer = "www.bibliomania.com";
464         }
465 
466           smtpServer =
467             getSettingTable().ensure(Email.SMTPSERVER,
468   defaultSmtpServer,
469     "SMTP server",
470     "The SMTP server through which auto-generated mail " + "should be sent");
471 
472         passwordReminderMessage =
473           (Setting)getSettingTable().getNameColumn().firstWhereEq(
474             "PasswordReminderMessage");
475         if (passwordReminderMessage == null) {
476           String it;
477           try {
478             it =
479               new String(
480                 IoUtils.slurp(
481                   this.getClass().getResource(defaultPasswordReminderMessage),
482                   4096));
483           } catch (Exception e) {
484             throw new UnexpectedExceptionException(
485               "Didn't find resource " + defaultPasswordReminderMessage,
486               e);
487           }
488 
489           passwordReminderMessage =
490             getSettingTable().ensure(
491               "PasswordReminderMessage",
492               it,
493               "Password reminder message",
494               "The text of the message sent to users to remind them of "
495                 + "their password (actually a WebMacro template)");
496           passwordReminderMessage.setWidth(80);
497           passwordReminderMessage.setHeight(30);
498         }
499 
500         passwordReminderFrom =
501           getSettingTable().ensure(
502             "PasswordReminderFrom",
503             defaultPasswordReminderFrom,
504             "Password reminder `From:'",
505             "The address from whom the emailed password reminders "
506               + "should originate");
507 
508         confirmationEmailFrom =
509           getSettingTable().ensure(
510             "ConfirmationEmailFrom",
511             defaultConfirmationEmailFrom,
512             "Confirmation Email `From:'",
513             "The address from whom the confirmation email that is sent on "
514               + "registration should originate");
515 
516         orderEmailFrom =
517           getSettingTable().ensure(
518             "OrderEmailFrom",
519             defaultOrderEmailFrom,
520             "Order Email `From:'",
521             "The address from whom the emailed order acknowledgements "
522               + "should originate");
523 
524         orderEmailTo =
525           getSettingTable().ensure(
526             "OrderEmailTo",
527             defaultOrderEmailTo,
528             "Order Email `To:'",
529             "The address to send email acknowledgements to (at bibliomania)");
530 
531         stratusEmailTo =
532           getSettingTable().ensure(
533             "StratusEmailTo",
534             defaultStratusEmailTo,
535             "Stratus Email `To:'",
536             "The address to send email orders to (at Stratus)");
537 
538         contentEncoding =
539           getSettingTable().ensure(
540             "Content encoding",
541             defaultContentEncoding,
542             "Encoding (character set) to use for content",
543             "The encoding, or character set, to use for the cached "
544               + "content; if you don't know why you need to set this, "
545               + "leave it as UTF8.");
546 
547         uploadDir =
548           getSettingTable().ensure(
549             "UploadDir",
550             defaultUploadDir,
551             "Upload Directory for Product Images",
552             "Upload Directory for product images.  This is prepended to the "
553               + "path used for the book to which this product relates.");
554 
555         uploadURL =
556           getSettingTable().ensure(
557             "UploadURL",
558             defaultUploadURL,
559             "URL used when finding uploaded images",
560             "The URL used when finding uploaded images.  This is prepended to the "
561               + "URL used for the book to which this product relates.");
562 
563         /* this is all a bit verbose, but it is nice to ensure these are present
564          */
565 
566         // Advert table - normal admin permissions
567         setDefaultAccessPermissions(
568           getAdvertTable().getTableInfo(),
569           contentAdditionCapability,
570           administerCapability(),
571           contentModificationCapability,
572           null);
573 
574         // Attachment table - only sys admins can add / change
575         // individual users can also change things
576         setDefaultAccessPermissions(
577           getAttachmentTable().getTableInfo(),
578           null,
579           administerCapability(),
580           administerCapability(),
581           null);
582 
583         // Attachment type table - only sys admins can add / change
584         setDefaultAccessPermissions(
585           getAttachmentTypeTable().getTableInfo(),
586           administerCapability(),
587           administerCapability(),
588           administerCapability(),
589           null);
590 
591         // Author table - normal admin permissions
592         setDefaultAccessPermissions(
593           getAuthorTable().getTableInfo(),
594           contentAdditionCapability,
595           administerCapability(),
596           contentModificationCapability,
597           null);
598 
599         // Author website table - normal admin permissions
600         setDefaultAccessPermissions(
601           getAuthorWebSiteTable().getTableInfo(),
602           contentAdditionCapability,
603           administerCapability(),
604           contentModificationCapability,
605           null);
606 
607         // messageboard table - normal admin permissions
608         setDefaultAccessPermissions(
609           getBoardTable().getTableInfo(),
610           contentAdditionCapability,
611           administerCapability(),
612           contentModificationCapability,
613           null);
614 
615         // messageboard type table - only administrators
616         setDefaultAccessPermissions(
617           getBoardTypeTable().getTableInfo(),
618           administerCapability(),
619           administerCapability(),
620           contentModificationCapability,
621           null);
622 
623         // book table - normal admin permissions
624         setDefaultAccessPermissions(
625           getBookTable().getTableInfo(),
626           contentAdditionCapability,
627           administerCapability(),
628           contentModificationCapability,
629           null);
630 
631         // book format table - only administrators
632         setDefaultAccessPermissions(
633           getBookFormatTable().getTableInfo(),
634           administerCapability(),
635           administerCapability(),
636           administerCapability(),
637           null);
638 
639         // bookshop table - only administrators - but others can edit
640         setDefaultAccessPermissions(
641           getBookshopTable().getTableInfo(),
642           administerCapability(),
643           administerCapability(),
644           contentModificationCapability,
645           null);
646 
647         // book stocking table - only administrators 
648         setDefaultAccessPermissions(
649           getBookStockingTable().getTableInfo(),
650           administerCapability(),
651           administerCapability(),
652           administerCapability(),
653           null);
654 
655         // chapter table - normal admin permissions
656         setDefaultAccessPermissions(
657           getChapterTable().getTableInfo(),
658           contentAdditionCapability,
659           administerCapability(),
660           contentModificationCapability,
661           null);
662 
663         // country table - normal admin permissions
664         setDefaultAccessPermissions(
665           getCountryTable().getTableInfo(),
666           contentAdditionCapability,
667           administerCapability(),
668           contentModificationCapability,
669           null);
670 
671         // currency table - normal admin permissions
672         setDefaultAccessPermissions(
673           getCurrencyTable().getTableInfo(),
674           contentAdditionCapability,
675           administerCapability(),
676           contentModificationCapability,
677           null);
678 
679         // delivery charge table - normal admin permissions
680         setDefaultAccessPermissions(
681           getDeliveryChargeTable().getTableInfo(),
682           contentAdditionCapability,
683           administerCapability(),
684           contentModificationCapability,
685           null);
686 
687         // delivery charge band table - normal admin permissions
688         setDefaultAccessPermissions(
689           getDeliveryChargeBandTable().getTableInfo(),
690           contentAdditionCapability,
691           administerCapability(),
692           contentModificationCapability,
693           null);
694 
695         // download table - normal admin permissions
696         setDefaultAccessPermissions(
697           getDownloadTable().getTableInfo(),
698           contentAdditionCapability,
699           administerCapability(),
700           contentModificationCapability,
701           null);
702 
703         // download event table - only administrator and the relevant user
704         setDefaultAccessPermissions(
705           getDownloadEventTable().getTableInfo(),
706           null,
707           administerCapability(),
708           administerCapability(),
709           administerCapability());
710 
711         // layout table - normal admin permissions
712         setDefaultAccessPermissions(
713           getLayoutTable().getTableInfo(),
714           contentAdditionCapability,
715           administerCapability(),
716           contentModificationCapability,
717           null);
718 
719         // membership status table - normal admin permissions
720         setDefaultAccessPermissions(
721           getMembershipStatusTable().getTableInfo(),
722           contentAdditionCapability,
723           administerCapability(),
724           contentModificationCapability,
725           null);
726 
727         // order table - owning user, and admins can modify
728         setDefaultAccessPermissions(
729           getShopOrderTable().getTableInfo(),
730           null,
731           administerCapability(),
732           null,
733           null);
734 
735         // order item table - owning user, and admins can modify
736         setDefaultAccessPermissions(
737           getShopOrderItemTable().getTableInfo(),
738           null,
739           administerCapability(),
740           null,
741           null);
742 
743         // order status table - anly admins can chnage 
744         setDefaultAccessPermissions(
745           getOrderStatusTable().getTableInfo(),
746           administerCapability(),
747           administerCapability(),
748           administerCapability(),
749           null);
750 
751         // product table - normal permissions
752         setDefaultAccessPermissions(
753           getProductTable().getTableInfo(),
754           contentAdditionCapability,
755           administerCapability(),
756           contentModificationCapability,
757           null);
758 
759         // product association table - normal permissions
760         setDefaultAccessPermissions(
761           getProductAssociationTable().getTableInfo(),
762           contentAdditionCapability,
763           administerCapability(),
764           contentModificationCapability,
765           null);
766 
767         // publisher table - normal permissions
768         setDefaultAccessPermissions(
769           getPublisherTable().getTableInfo(),
770           contentAdditionCapability,
771           administerCapability(),
772           contentModificationCapability,
773           null);
774 
775         // publisher table - normal permissions
776         setDefaultAccessPermissions(
777           getPublisherTable().getTableInfo(),
778           contentAdditionCapability,
779           administerCapability(),
780           contentModificationCapability,
781           null);
782 
783         // section table - normal permissions
784         setDefaultAccessPermissions(
785           getSectionTable().getTableInfo(),
786           contentAdditionCapability,
787           administerCapability(),
788           contentModificationCapability,
789           null);
790 
791         // section group table - admin only
792         setDefaultAccessPermissions(
793           getSectionTable().getTableInfo(),
794           administerCapability(),
795           administerCapability(),
796           administerCapability(),
797           null);
798 
799         // Sex table - admin only
800         setDefaultAccessPermissions(
801           getSexTable().getTableInfo(),
802           administerCapability(),
803           administerCapability(),
804           administerCapability(),
805           null);
806 
807         // Stockingssearch table - admin only
808         setDefaultAccessPermissions(
809           getStockingsSearchTable().getTableInfo(),
810           null,
811           administerCapability(),
812           administerCapability(),
813           null);
814 
815         // Supplier table - normal permission
816         setDefaultAccessPermissions(
817           getSupplierTable().getTableInfo(),
818           contentAdditionCapability,
819           administerCapability(),
820           contentModificationCapability,
821           null);
822 
823         // Supplier products table - normal permission
824         setDefaultAccessPermissions(
825           getSupplierProductTable().getTableInfo(),
826           contentAdditionCapability,
827           administerCapability(),
828           contentModificationCapability,
829           null);
830 
831         // User table - owner and admin only
832         setDefaultAccessPermissions(
833           getUserTable().getTableInfo(),
834           administerCapability(),
835           administerCapability(),
836           administerCapability(),
837           administerCapability());
838       }
839     });
840 
841     if (bookStockingsInBackgroundAppropriate)
842       new BackgroundStockingsChecker().start();
843 
844     /*
845      * Here is a good place to put one off hacks.
846      */
847      
848   }
849 
850   private void setDefaultAccessPermissions(
851     TableInfo info,
852     Capability add,
853     Capability delete,
854     Capability write,
855     Capability read) {
856     if (info.getCancreate() == null)
857       info.setCancreate(add);
858     if (info.getDefaultcandelete() == null)
859       info.setDefaultcandelete(delete);
860     if (info.getDefaultcanwrite() == null)
861       info.setDefaultcanwrite(write);
862     if (info.getDefaultcanread() == null)
863       info.setDefaultcanread(read);
864   }
865 
866   public IndexOther fti() {
867     return fti;
868   }
869 
870   public IndexOther infoFTI() {
871     return infoFTI;
872   }
873 
874   Library infoLibrary = new Library() {
875     public Text text(long textID) {
876       int a =
877         (int) ((textID & ((1l << Chapter.ftiTextID_section_shift) - 1))
878           >> Chapter.ftiTextID_author_shift);
879 
880       long aMask = (1l << Chapter.ftiTextID_author_shift) - 1;
881 
882       if ((textID & aMask) == aMask) {
883         // it's an author
884         // but be defensive about finding it
885         try {
886           return getAuthorTable().getAuthorObject(new Integer(a));
887         } catch (NoSuchRowPoemException e) {
888           // log it
889           e.printStackTrace(System.err);
890           return null;
891         }
892       } else {
893         // it's a book
894         int b = (int) ((textID & aMask) >> Chapter.ftiTextID_book_shift);
895         return (Book)getBookTable().firstSelection(
896           "author = " + a + " AND " + "authorsequence = " + b);
897       }
898     }
899   };
900 
901   public Library infoLibrary() {
902     return infoLibrary;
903   }
904 
905   public Pagination pagination() {
906     return pagination;
907   }
908 
909   public String getWorkspaceDir() {
910     return workspaceDir.getStringCooked();
911   }
912 
913   public String getUploadDir() {
914     return uploadDir.getStringCooked();
915   }
916 
917   public String getUploadURL() {
918     return uploadURL.getStringCooked();
919   }
920 
921   public String getInfoFTIDir() {
922     return getWorkspaceDir() + infoFTIDir.getStringCooked();
923   }
924 
925   public String getContentRootDir() {
926     return contentRootDir.getStringCooked();
927   }
928 
929   private WebMacro _wm = null;
930 
931   public WebMacro getWebMacro() {
932     if (_wm == null) {
933       synchronized (this) {
934         if (_wm == null) {
935           try {
936             _wm = new WM();
937           } catch (InitException e) {
938             throw new UnexpectedExceptionException(
939               "Initialising WebMacro for the pagination subsystem",
940               e);
941           }
942         }
943       }
944     }
945 
946     return _wm;
947   }
948 
949   public void 
950     setupContext( Melati melati, Context context, Unit it, Hashtable extras) {
951 
952     SectionGroup sectiongroup = it == null ? null : it.getReadArea();
953     context.put("sectiongroup", sectiongroup);
954     if (sectiongroup != null) {
955       context.put("areaColour", sectiongroup.getThemecolour());
956     } else {
957       context.put("areaColour", "#FF0000");
958     }
959 
960     Section section = null;
961     Author author = null;
962     Book book = null;
963     Chapter chapter = null;
964 
965     if (it != null) {
966       context.put("meta_description", it.getMetatag_description());
967       context.put("meta_keywords", it.getMetatag_keywords());
968 
969       if (it instanceof Chapter) {
970         chapter = (Chapter)it;
971         book = chapter.getBook();
972         author = book.getAuthor();
973         section = book.getSection();
974       } else if (it instanceof Book) {
975         book = (Book)it;
976         author = book.getAuthor();
977         section = book.getSection();
978       } else if (it instanceof Author) {
979         author = (Author)it;
980       } else if (it instanceof Section) {
981         section = (Section)it;
982       }
983     }
984 
985     context.put("object", it);
986     context.put("section", section);
987     context.put("author", author);
988     context.put("book", book);
989     context.put("chapter", chapter);
990     context.put("bib", getBib());
991     context.put("db", this);
992     context.put("melati", melati);
993     if (extras != null)
994       for (Enumeration k = extras.keys(); k.hasMoreElements();) {
995         String key = (String)k.nextElement();
996         context.put(key, extras.get(key));
997       }
998   }
999 
1000   public final void setupContext(Melati melati, Context context, Unit it) {
1001     setupContext(melati, context, it, null);
1002   }
1003 
1004   public void templateExpand(
1005     org.webmacro.Template template,
1006     OutputStream o,
1007     Unit object,
1008     Hashtable extras)
1009     throws WebMacroException, IOException {
1010     WebMacro wm = getWebMacro();
1011 
1012     MelatiFastWriter fmw =
1013       new MelatiFastWriter(wm.getBroker(), o, getContentEncoding());
1014     Melati m = new Melati(new MelatiConfig(), fmw);
1015     Context context = wm.getContext();
1016     setupContext(m, context, object, extras);
1017     template.write(fmw.getPeer().getOutputStream(), context);
1018     fmw.flush();
1019     /*
1020         Melati m = new Melati(new MelatiConfig(),
1021                               new SimpleMelatiWriter(new OutputStreamWriter(o)));
1022         m.setBufferingOff(false);
1023         Context context = wm.getContext();
1024         setupContext(m, context, object, extras);
1025         template.write(new FastWriter(o, getContentEncoding()), context);
1026     */
1027   }
1028 
1029   public final void templateExpand(
1030     org.webmacro.Template template,
1031     OutputStream o,
1032     Unit object)
1033     throws WebMacroException, IOException {
1034     templateExpand(template, o, object, null);
1035   }
1036 
1037   /**
1038    * Hook to be called when content files are created or changed.  At the
1039    * moment, this makes the file executable (<TT>chmod</TT> <TT>ugo+x</TT>),
1040    * because we use mod-include (server-side includes) to serve the ad banners,
1041    * and Apache will only send a <TT>Last-Modified</TT> in the header if the
1042    * <TT>XBitHack</TT> option is enabled in <TT>httpd.conf</TT> and the
1043    * <TT>x</TT> bit is set.  For a while we feared that if Apache doesn't send
1044    * <TT>Last-Modified</TT>, Google doesn't index us; that seems not to be
1045    * true, but nevertheless sending <TT>Last-Modified</TT> is a good thing.
1046    */
1047 
1048   public static void notifyNewContentFile(File file) throws IOException {
1049     FileUtils.makeExecutable(file);
1050   }
1051 
1052   public void templateExpandNamed(
1053     String templateName,
1054     File to,
1055     Unit object,
1056     Hashtable extras)
1057     throws WebMacroException, IOException {
1058     org.webmacro.Template template = getWebMacro().getTemplate(templateName);
1059     OutputStream o = new BufferedOutputStream(new FileOutputStream(to));
1060     try {
1061       templateExpand(template, o, object, extras);
1062     } finally {
1063       try {
1064         o.close();
1065       } catch (Exception e) {
1066       }
1067     }
1068 
1069     notifyNewContentFile(to);
1070   }
1071 
1072   public final void templateExpandNamed(
1073     String templateName,
1074     File to,
1075     Unit object)
1076     throws WebMacroException, IOException {
1077     templateExpandNamed(templateName, to, object, null);
1078   }
1079 
1080   public void macroexpand(Reader i, OutputStream o, Unit object)
1081     throws WebMacroException, IOException {
1082     templateExpand(new StreamTemplate(getWebMacro().getBroker(), i), o, object);
1083     //    templateExpand(new StreamTemplate(getWebMacro().getBroker(), i, getContentEncoding()),
1084     //                   o, object);
1085   }
1086 
1087   public void macroexpand(File from, OutputStream o, Unit object)
1088     throws WebMacroException, IOException {
1089     Reader i = new BufferedReader(new FileReader(from));
1090     try {
1091       macroexpand(i, o, object);
1092     } finally {
1093       try {
1094         i.close();
1095       } catch (Exception e) {
1096       }
1097     }
1098   }
1099 
1100   public void macroexpand(File from, File to, Unit object)
1101     throws WebMacroException, IOException {
1102     OutputStream o = new BufferedOutputStream(new FileOutputStream(to));
1103     try {
1104       macroexpand(from, o, object);
1105     } finally {
1106       try {
1107         o.close();
1108       } catch (Exception e) {
1109       }
1110     }
1111   }
1112 
1113   public static class TemplateException extends MelatiRuntimeException {
1114     /**
1115      * 
1116      */
1117     private static final long serialVersionUID = 1L;
1118     public File file;
1119 
1120     public TemplateException(File file, Exception trouble) {
1121       super(trouble);
1122       this.file = file;
1123     }
1124 
1125     public String getMessage() {
1126       return "Couldn't load templet from " + file + "\n" + subException;
1127     }
1128   }
1129 
1130   private org.webmacro.Template template(String source) {
1131     File file = new File(getContentRootDir(), source);
1132 
1133     org.webmacro.Template it =
1134       new FileTemplate(getWebMacro().getBroker(), file, getContentEncoding());
1135     try {
1136       it.parse();
1137     } catch (Exception e) {
1138       throw new TemplateException(file, e);
1139     }
1140 
1141     return it;
1142   }
1143 
1144   public void writeContentHeader(OutputStream w, Unit object, String template)
1145     throws WebMacroException, IOException {
1146     templateExpand(
1147       template(template == null ? "header.wm" : template),
1148       w,
1149       object);
1150   }
1151 
1152   public void writeContentFooter(OutputStream w, Unit object, String template)
1153     throws WebMacroException, IOException {
1154     templateExpand(
1155       template(template == null ? "footer.wm" : template),
1156       w,
1157       object);
1158   }
1159 
1160   private org.webmacro.Template footnoteTemplate = null;
1161 
1162   public org.webmacro.Template getFootnoteTemplate() {
1163     if (footnoteTemplate == null)
1164       footnoteTemplate = template("footnote.wm");
1165 
1166     return footnoteTemplate;
1167   }
1168 
1169   public String getPasswordReminderMessage() {
1170     return passwordReminderMessage.getStringCooked();
1171   }
1172 
1173   public org.webmacro.Template getPasswordReminderTemplate() {
1174     return new StreamTemplate(
1175       getWebMacro().getBroker(),
1176       new StringReader(getPasswordReminderMessage().trim()));
1177     //        new StringReader(getPasswordReminderMessage().trim()),getContentEncoding());
1178   }
1179 
1180   public String getPasswordReminderFrom() {
1181     return passwordReminderFrom.getStringCooked();
1182   }
1183 
1184   public String getOrderEmailFrom() {
1185     return orderEmailFrom.getStringCooked();
1186   }
1187 
1188   public String getConfirmationEmailFrom() {
1189     return confirmationEmailFrom.getStringCooked();
1190   }
1191 
1192   public String getOrderEmailTo() {
1193     return orderEmailTo.getStringCooked();
1194   }
1195 
1196   public String getStratusEmailTo() {
1197     return stratusEmailTo.getStringCooked();
1198   }
1199 
1200   public String getCachedContentRootDir() {
1201     return getWorkspaceDir() + "/cached";
1202   }
1203 
1204   public String getStaticRootURL() {
1205     return staticRootURL.getStringCooked();
1206   }
1207 
1208   public String getContentStaticRootURL() {
1209     return contentStaticRootURL.getStringCooked();
1210   }
1211 
1212   public String getBannerURLPath() {
1213     return bannerURLPath.getStringCooked();
1214   }
1215 
1216   public String getCacheRootURL() {
1217     String it = cacheRootURL.getStringCooked();
1218     return it == null ? "" : it;
1219   }
1220 
1221   public String getHomepageURL() {
1222     return homepageURL.getStringCooked();
1223   }
1224 
1225   public String getSmtpServer() {
1226     return smtpServer.getStringCooked();
1227   }
1228 
1229   public int getDefaultSearchHitsPerText() {
1230     Integer it = defaultSearchHitsPerText.getIntegerCooked();
1231     return it == null ? defaultDefaultSearchHitsPerText : it.intValue();
1232   }
1233 
1234   public int getSearchHitsPerPage() {
1235     Integer it = searchHitsPerPage.getIntegerCooked();
1236     return it == null ? defaultSearchHitsPerPage : it.intValue();
1237   }
1238 
1239   public String getPaginationTexHeader() {
1240     return paginationTexHeader.getStringCooked();
1241   }
1242 
1243   public int getBookStockingsCheckIntervalDays() {
1244     Integer it = bookStockingsCheckIntervalDays.getIntegerCooked();
1245     return it == null ? defaultBookStockingsCheckIntervalDays : it.intValue();
1246   }
1247 
1248   public long getBookStockingsCheckIntervalMillis() {
1249     return (long)getBookStockingsCheckIntervalDays() * 1000L * 60L * 60L * 24L;
1250   }
1251 
1252   public int getBibBookBookshopSearchTimeoutSeconds() {
1253     Integer it = bibBookBookshopSearchTimeoutSeconds.getIntegerCooked();
1254     return it == null
1255       ? defaultBibBookBookshopSearchTimeoutSeconds
1256       : it.intValue();
1257   }
1258 
1259   public long getBibBookBookshopSearchTimeoutMillis() {
1260     return (long)getBibBookBookshopSearchTimeoutSeconds() * 1000L;
1261   }
1262 
1263   public int getBookStockingsOutputStartOffset() {
1264     Integer it = bookStockingsOutputStartOffset.getIntegerCooked();
1265     return it == null ? defaultBookStockingsOutputStartOffset : it.intValue();
1266   }
1267 
1268   public String getBookStockingsCacheDir() {
1269     return bookStockingsCacheDir.getStringCooked();
1270   }
1271 
1272   public int getBookStockingsCacheSizeMax() {
1273     Integer it = bookStockingsCacheSizeMax.getIntegerCooked();
1274     return it == null ? defaultBookStockingsCacheSizeMax : it.intValue();
1275   }
1276 
1277   public Integer pageFromAnchor(String anchor) {
1278     if (anchor.startsWith(Pagination.pageAnchorPrefix))
1279       try {
1280         return new Integer(
1281           Integer.parseInt(
1282             anchor.substring(Pagination.pageAnchorPrefix.length()))
1283             + 1);
1284       } catch (NumberFormatException e) {
1285         return null;
1286       } else
1287       return null;
1288   }
1289 
1290   public static class BookshopException extends MelatiRuntimeException {
1291     /**
1292      * 
1293      */
1294     private static final long serialVersionUID = 1L;
1295     public String which;
1296 
1297     public BookshopException(String which, Exception e) {
1298       super(e);
1299       this.which = which;
1300     }
1301 
1302     public String getMessage() {
1303       return "Something went wrong starting the "
1304         + which
1305         + " subsystem\n"
1306         + subException.getMessage();
1307     }
1308   }
1309 
1310   private BookshopBackend bookshopBackendNamed(String packageComp) {
1311     try {
1312       return (
1313         (BookshopBackendFactory)Class
1314           .forName(
1315             "org.paneris.bibliomania.metasearch." + packageComp + ".Factory")
1316           .newInstance())
1317           .instance(
1318         new File(getWorkspaceDir()),
1319         0);
1320     } catch (Exception e) {
1321       throw new BookshopException(packageComp, e);
1322     }
1323   }
1324 
1325   public Bookshop[] bookshops() {
1326     return bookshops;
1327   }
1328 
1329   public SectionGroup getReadSectionGroup() {
1330     return readSectionGroup;
1331   }
1332 
1333   public Currency getUKCurrency() {
1334     return UKCurrency;
1335   }
1336 
1337   public SectionGroup getStudySectionGroup() {
1338     return studySectionGroup;
1339   }
1340 
1341   public SectionGroup getResearchSectionGroup() {
1342     return researchSectionGroup;
1343   }
1344 
1345   public SectionGroup getShopSectionGroup() {
1346     return shopSectionGroup;
1347   }
1348 
1349   public SectionGroup getSearchSectionGroup() {
1350     return searchSectionGroup;
1351   }
1352 
1353   public Group getRegisteredUserGroup() {
1354     return registeredUserGroup;
1355   }
1356 
1357   public Capability getRegisteredUserCapability() {
1358     return registeredUserCapability;
1359   }
1360   public Capability getContentModificationCapability() {
1361     return contentModificationCapability;
1362   }
1363   public Capability getContentAdditionCapability() {
1364     return contentAdditionCapability;
1365   }
1366 
1367   public User getTemplateRegisterUser() {
1368     return templateRegisterUser;
1369   }
1370 
1371   private Bib bib = null;
1372 
1373   public Bib getBib() {
1374     if (bib == null)
1375       bib = new Bib(this);
1376     return bib;
1377   }
1378 
1379   private int bookshopSearchesRunning = 0;
1380 
1381   public void notifyBookshopSearchStart() {
1382     ++bookshopSearchesRunning;
1383   }
1384 
1385   public void notifyBookshopSearchStop() {
1386     --bookshopSearchesRunning;
1387   }
1388 
1389   public boolean okToRunABookshopSearch() {
1390     Integer maxO = bookStockingsCheckConcurrentMax.getIntegerCooked();
1391     int max =
1392       maxO == null ? defaultBookStockingsCheckConcurrentMax : maxO.intValue();
1393 
1394     return bookshopSearchesRunning < max;
1395   }
1396 
1397   public boolean getBookStockingsInBackground() {
1398     return bookStockingsInBackgroundAppropriate
1399       && Boolean.TRUE.equals(bookStockingsInBackground.getBooleanCooked());
1400   }
1401 
1402   public String getContentEncoding() {
1403     return contentEncoding.getStringCooked();
1404   }
1405 
1406   private class BackgroundStockingsChecker extends Thread {
1407 
1408     private PreparedStatementFactory booksPS, booksPSNull;
1409 
1410     BackgroundStockingsChecker() {
1411       booksPSNull =
1412         new PreparedStatementFactory(
1413           BibliomaniaDatabase.this,
1414           "SELECT book.id FROM book WHERE lastbookshopsearch IS NULL AND "
1415             + "(hasnofrontpage IS NULL OR NOT hasnofrontpage) AND "
1416             + "(author.id = book.author AND "
1417             + "(author.nonstandard IS NULL OR NOT author.nonstandard)) "
1418             + "LIMIT 1");
1419 
1420       booksPS =
1421         new PreparedStatementFactory(
1422           BibliomaniaDatabase.this,
1423           "SELECT id FROM book WHERE lastbookshopsearch < ? AND "
1424             + "(hasnofrontpage IS NULL OR NOT hasnofrontpage) AND "
1425             + "(author.id = book.author AND "
1426             + "(author.nonstandard IS NULL OR NOT author.nonstandard)) "
1427             + "ORDER BY lastbookshopsearch "
1428             + "LIMIT 1");
1429     }
1430 
1431     private void doNext() throws Exception {
1432       inSession(AccessToken.root, new PoemTask() {
1433         public void run() {
1434           Book nextToDo;
1435           PreparedStatement ps = null;
1436 
1437           try {
1438             if (!getBookStockingsInBackground())
1439               nextToDo = null;
1440             else {
1441               Integer bookTroid = null;
1442 
1443               ps = booksPSNull.preparedStatement(PoemThread.transaction());
1444               ResultSet rs = ps.executeQuery();
1445               try {
1446                 if (rs.next())
1447                   bookTroid = new Integer(rs.getInt(1));
1448               } finally {
1449                 rs.close();
1450               }
1451 
1452               if (bookTroid == null) {
1453                 ps = booksPS.preparedStatement(PoemThread.transaction());
1454                 ps.setTimestamp(
1455                   1,
1456                   new Time