View Javadoc

1   /*
2    * $Source: /usr/cvsroot/MelatiShopping/src/main/java/org/paneris/melati/shopping/ShoppingTrolley.java,v $
3    * $Revision: 1.25 $
4    *
5    * Copyright (C) 2000 Tim Joyce
6    *
7    * Part of Melati (http://melati.org/ ), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati if you would like 
30   * to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Tim Joyce <timj@paneris.org>
42   *     http://paneris.org/~timj/
43   *     68 Sandbanks Rd, Poole, Dorset. BH14 8BY. UK
44   */
45  
46  package org.paneris.melati.shopping;
47  
48  import java.util.Hashtable;
49  import java.util.Vector;
50  import java.util.Enumeration;
51  import org.melati.Melati;
52  import org.melati.servlet.Form;
53  import org.melati.template.ServletTemplateContext;
54  import javax.servlet.http.HttpSession;
55  import java.text.NumberFormat;
56  import java.util.Locale;
57  import org.melati.util.InstantiationPropertyException;
58  
59   /**
60   * <p> A Shopping Trolley stores information in the user's 
61   * Shopping Trolley.</p>
62   * <p> It does this by storing itself in the session.</p>
63   * <p> For this reason, the constructors are private, and you will be expected
64   * always to get the Shopping Trolley using getInstance();</p>
65   * <p>
66   * Usage example:
67   * </p><p>
68   * ShoppingTrolley trolley = ShoppingTrolley.getInstance(Melati melati);
69   * context.put("trolley", trolley);
70   * </p>
71   **/
72  
73  public abstract class ShoppingTrolley {
74  
75    private static String TROLLEY = 
76        "org.paneris.melati.shopping.DefaultShoppingTrolley";
77    protected Locale locale;
78    protected String address;
79    protected String name;
80    protected String tel;
81    protected String town;
82    protected String county;
83    protected String country;
84    protected String postcode;
85    protected String message;
86    protected String email;
87    protected boolean hasDetails = false;
88    Vector orderedItems = new Vector();
89    Hashtable items = new Hashtable();
90  
91    public static final double VAT_PERCENT_TIMES_TEN = 175.0;
92    public MelatiShoppingConfig config;
93    public Melati melati;
94  
95   /**
96    * Private Constructor to build an empty ShoppingTrolley
97    **/
98    protected void initialise(Melati melatiIn, MelatiShoppingConfig configIn) {
99      this.config = configIn;
100     this.melati = melatiIn;
101   }
102 
103   /**
104    * Public Constructor to build a trolley from some id.
105    */
106   public void initialise(Melati melatiIn, 
107                          MelatiShoppingConfig configIn, Integer id)
108    throws InstantiationPropertyException {
109     initialise(melatiIn,configIn);
110     load(id);
111     HttpSession session = melati.getSession();
112     session.setAttribute(name(),this);
113   }
114 
115   /**
116    * Remove any trolley from the session.
117    */
118   public void remove(Melati melatiIn) {
119     HttpSession session = melatiIn.getSession();
120     session.removeAttribute(name());
121   }
122 
123 
124   /**
125    * Returns the single instance, creating one if it can't be found.
126    */
127   public static synchronized ShoppingTrolley 
128       getInstance(Melati melati, MelatiShoppingConfig config)
129    throws InstantiationPropertyException {
130     HttpSession session = melati.getSession();
131     ShoppingTrolley instance = (ShoppingTrolley) session.getAttribute(name());
132     if (instance == null) {
133       instance = newTrolley(config);
134       instance.initialise(melati,config);
135       session.setAttribute(name(),instance);
136     }
137     instance.configureRequest(melati);
138     return instance;
139   }
140 
141   public static synchronized ShoppingTrolley 
142       newTrolley(MelatiShoppingConfig config)
143       throws InstantiationPropertyException {
144     return config.getShoppingTrolley();
145   }
146 
147  /**
148   * Get the Locale for this trolley.
149   */
150   public abstract Locale getLocale();
151 
152 
153  /**
154   * Set the Locale for this trolley.
155   */
156   public void setLocale(Locale locale){
157     this.locale = locale;
158   }
159 
160  /**
161   * Confirm payment of this trolley.
162   */
163   public abstract void confirmPayment(Melati melatiIn);
164 
165  /**
166   * Load a trolley from something persistent.
167   */
168   public abstract void load(Integer id) throws InstantiationPropertyException;
169 
170  /**
171   * Save a trolley to something persistent.
172   */
173   public abstract void save();
174 
175  /**
176   * This is done for each request, so anything special that needs to be done
177   * can be put in here
178   */
179   public void configureRequest(Melati melatiIn) {
180     this.melati = melatiIn;
181   }
182 
183   /**
184    *  Do something to force users to login.
185    * You could perhaps throw an access poem exception in order to let the
186    * servlet generate the login page.
187    */
188   public abstract void assertLogin(Melati melatiIn);
189 
190   /**
191    * Set the user's detault details into this trolley.  
192    * This is useful if users have already logged in, 
193    * and we don't want them to reenter their details.
194    */
195   public abstract void setDefaultDetails(Melati melatiIn);
196 
197 
198  /**
199   * Return the name of the trolley (for storing in the session).
200   */
201   public static String name() {
202     return TROLLEY;
203   }
204 
205  /**
206   * Get the items from the trolley.
207   */
208   public Enumeration getItems() {
209     return orderedItems.elements();
210   }
211 
212  /**
213   * Have we got anything in the trolley.
214   */
215   public boolean isEmpty() {
216     return items.isEmpty();
217   }
218 
219  /**
220   * Have we entered any personal information.
221   */
222   public boolean hasDetails() {
223     return hasDetails;
224   }
225 
226  /**
227   * Get an item from the trolley.
228   */
229   public ShoppingTrolleyItem getItem(Integer id) {
230     return (ShoppingTrolleyItem)items.get(id);
231   }
232 
233  /**
234   * Remove an item from the trolley.
235   */
236   public void removeItem(ShoppingTrolleyItem item) {
237     items.remove(item.getId());
238     orderedItems.removeElement(item);
239   }
240 
241  /**
242   * Add an item to the trolley.
243   */
244   public void addItem(ShoppingTrolleyItem item) {
245     // don't add it if it's already there
246     if (!items.containsKey(item.getId())) {
247       orderedItems.add(item);
248     }
249     items.put(item.getId(),item);
250   }
251 
252  /**
253   * Create an item and put it in the Trolley.
254   * 
255   * @param id
256   * @param description
257   * @param price
258   * @return a newly created item in the Trolley
259   * @throws InstantiationPropertyException
260   */
261   public ShoppingTrolleyItem newItem(Integer id, String description, 
262                                      Double price)
263    throws InstantiationPropertyException {
264     ShoppingTrolleyItem item = ShoppingTrolleyItem.newTrolleyItem(config);
265     item.initialise(this, melati, id, description, price);
266     addItem(item);
267     return item;
268   }
269 
270  /**
271   * Calculate the value of the items in the trolley.
272   */
273   public double getValue() {
274     double value = 0;
275     for (Enumeration en = items.elements(); en.hasMoreElements();) {
276       ShoppingTrolleyItem product = (ShoppingTrolleyItem) en.nextElement();
277       value += product.getValue();
278     }
279     return value;
280   }
281 
282  /**
283   * Format the order value for display.
284   * This value does not include discount or delivery, but does invlude VAT.
285   */
286   public String getValueDisplay() {
287     return displayCurrency(getValue());
288   }
289 
290  /**
291   * Calculate the total value of this order.
292   */
293   public double getTotalValue() {
294     return getValue() + 
295            getTotalDeliveryValue() + 
296            getDiscountValue() + 
297            getVATValue();
298   }
299 
300  /**
301   * Format the total order value for display.
302   * This value includes discount, delivery and VAT.
303   */
304   public String getTotalValueDisplay() {
305     return displayCurrency(getTotalValue());
306   }
307 
308  /**
309   * Format the total order value in pence, typically ecomerce sites
310   * accept the values in pence not pounds.
311   */
312   public String getTotalValuePence() {
313     return (new Double(roundTo2dp(getTotalValue() * 100))).intValue() + "";
314   }
315 
316  /**
317   * Provide a mechanism for working out if
318   * this order should include a delivery charge.
319   */
320   public abstract boolean hasDelivery();
321 
322  /**
323   * You need to provide some mechanism for calculating the delivery
324   * value for the order (item delivery values are calculated individually.
325   */
326   public abstract double getDeliveryValue();
327 
328  /**
329   * The delivery charge for the order is the sum of the charges on the items
330   * and an overall charge.
331   */
332   public double getTotalDeliveryValue() {
333     double value = 0;
334     if (hasDelivery()) {
335       value = getDeliveryValue();
336       for (Enumeration en = items.elements(); en.hasMoreElements();) {
337         ShoppingTrolleyItem item = (ShoppingTrolleyItem)en.nextElement();
338         value += item.getDeliveryValue();
339       }
340     }
341     return value;
342   }
343 
344  /**
345   * Format the delivery value for display.
346   */
347   public String getDeliveryDisplay() {
348     return displayCurrency(getTotalDeliveryValue());
349   }
350 
351  /**
352   * Provide a mechanism for working out if
353   * this order should include a discount.
354   */
355   public abstract boolean hasDiscount();
356 
357  /**
358   * If you want to apply a discount to this order, do it here.
359   */
360   public abstract double getDiscountRate();
361 
362  /**
363   * Work out the value of the discout applied to this order
364   * (returns a negative value).
365   */
366   public double getDiscountValue() {
367     double value = 0;
368     if (hasDiscount()) {
369       value = 0 - roundTo2dp(getValue()*getDiscountRate());
370     }
371     return value;
372   }
373 
374  /**
375   * Display the discount (if present).
376   */
377   public String getDiscountRateDisplay() {
378     if (hasDiscount()) {
379       try {
380         return (new Double(getDiscountRate())).intValue() + "%";
381       } catch (NumberFormatException e) {
382         return getDiscountRate() + "%";
383       }
384     } else {
385       return "";
386     }
387   }
388 
389  /**
390   * Format the discount value for display.
391   */
392   public String getDiscountValueDisplay() throws Exception {
393     return displayCurrency(getDiscountValue());
394   }
395 
396  /**
397   * Provide a mechanism for working out if
398   * this order should include VAT (default should be true).
399   */
400   public abstract boolean hasVAT();
401 
402  /**
403   * Calculate the VAT value of the order.
404   * Typically items are priced inclusive of VAT and orders
405   * are therefore also inclusive of VAT.  If this order is
406   * for someone who should not be charged VAT, we need to subtract VAT
407   * from the order value.
408   */
409   public double getVATValue() {
410     if (!hasVAT()) {
411       return roundTo2dp((getValue() * 
412                         (-1 * (1.0 - (1000.0/VAT_PERCENT_TIMES_TEN)))));
413     } else {
414       return 0;
415     }
416   }
417 
418  /**
419   * Format the vat value for display.
420   */
421   public String getVATDisplay() {
422     return displayCurrency(getVATValue());
423   }
424 
425  /**
426   * Set values from form fileds. 
427   * @param melati
428   */
429   public void setFromForm(Melati melati) {
430     ServletTemplateContext tc = melati.getServletTemplateContext();
431     setName(Form.getFormNulled(tc,"trolley_name"));
432     setEmail(Form.getFormNulled(tc,"trolley_email"));
433     setTel(Form.getFormNulled(tc,"trolley_tel"));
434     setDeliveryAddress(Form.getFormNulled(tc,"trolley_deliveryaddress"));
435     setTown(Form.getFormNulled(tc,"trolley_town"));
436     setCounty(Form.getFormNulled(tc,"trolley_county"));
437     setCountry(Form.getFormNulled(tc,"trolley_country"));
438     setPostcode(Form.getFormNulled(tc,"trolley_postcode"));
439     setMessage(Form.getFormNulled(tc,"trolley_message"));
440     hasDetails = true;
441   }
442 
443  /**
444   * Set the address.
445   */
446   public void setDeliveryAddress(String a) {
447     address = a;
448   }
449  /**
450   * Get the address.
451   */
452   public String getDeliveryAddress() {
453     return address;
454   }
455 
456  /**
457   * Set the name.
458   */
459   public void setName(String a) {
460     name = a;
461   }
462  /**
463   * Get the name.
464   */
465   public String getName() {
466     return name;
467   }
468 
469  /**
470   * Set the email address.
471   */
472   public void setEmail(String a) {
473     email = a;
474   }
475  /**
476   * Get the email address.
477   */
478   public String getEmail() {
479     return email;
480   }
481 
482  /**
483   * Set the postcode.
484   */
485   public void setPostcode(String a) {
486     postcode = a;
487   }
488  /**
489   * Get the postcode.
490   */
491   public String getPostcode() {
492     return postcode;
493   }
494 
495  /**
496   * Set the telephone number.
497   */
498   public void setTel(String a) {
499     tel = a;
500   }
501  /**
502   * Get the telephone number.
503   */
504   public String getTel() {
505     return tel;
506   }
507 
508  /**
509   * Set the town.
510   */
511   public void setTown(String a) {
512     town = a;
513   }
514  /**
515   * Get the town.
516   */
517   public String getTown() {
518     return town;
519   }
520 
521  /**
522   * Set the county.
523   */
524   public void setCounty(String a) {
525     county = a;
526   }
527  /**
528   * Get the county.
529   */
530   public String getCounty() {
531     return county;
532   }
533 
534  /**
535   * Set the country.
536   */
537   public void setCountry(String a) {
538     country = a;
539   }
540  /**
541   * Get the country.
542   */
543   public String getCountry() {
544     return country;
545   }
546 
547  /**
548   * Set the delivery message.
549   */
550   public void setMessage(String a) {
551     message = a;
552   }
553  /**
554   * Get the delivery message.
555   */
556   public String getMessage() {
557     return message;
558   }
559 
560  /**
561   * Format a number in the locale currency.
562   */
563   public String displayCurrency(double value) {
564     return new String(NumberFormat.getCurrencyInstance(
565                                        getLocale()).format(value));
566   }
567 
568  /**
569   * Format a number in the locale currency.
570   */
571   public String displayCurrency(Double value) {
572     return displayCurrency(value.doubleValue());
573   }
574 
575   public String baseURL() {
576     return melati.getRequest().getServletPath() + "/" +
577            melati.getPoemContext().getLogicalDatabase() + "/";
578   }
579 
580   public String viewURL() {
581     return baseURL() + "View/";
582   }
583 
584   public String detailsURL() {
585     return baseURL() + "Details/";
586   }
587 
588   public String confirmURL() {
589     return baseURL() + "Confirm/";
590   }
591 
592   public String abandonURL() {
593     return baseURL() + "Abandon/";
594   }
595 
596   public String updateURL() {
597     return baseURL() + "Update/";
598   }
599 
600   public String paidURL() {
601     return baseURL() + "Paid/";
602   }
603 
604   public static double roundTo2dp(double num) {
605     int a = Math.round(new Float(num * 100).floatValue());
606     double b = new Double(a).doubleValue();
607     return (b/100);
608   }
609 
610 }
611