View Javadoc

1   /*
2    * $Source: /usr/cvsroot/MelatiShopping/src/main/java/org/paneris/melati/shopping/Trolley.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 org.melati.Melati;
49  import org.melati.servlet.Form;
50  import org.melati.util.MelatiException;
51  import org.melati.util.InstantiationPropertyException;
52  import org.melati.template.ServletTemplateContext;
53  import org.melati.servlet.TemplateServlet;
54  import org.melati.PoemContext;
55  import org.melati.servlet.PathInfoException;
56  import org.melati.servlet.InvalidUsageException;
57  import java.util.Enumeration;
58  import javax.servlet.ServletConfig;
59  import javax.servlet.ServletException;
60  
61  
62  /** 
63   * A servlet that handles the user's interaction with 
64   * the Shopping Trolley.
65   *
66   * @see org.paneris.melati.shopping.ShoppingTrolley
67   * @see org.paneris.melati.shopping.ShoppingTrolleyItem
68   * @see org.paneris.melati.shopping.DefaultShoppingTrolley
69   * @see org.paneris.melati.shopping.DefaultShoppingTrolleyItem
70   *
71   **/
72  
73  public class Trolley extends TemplateServlet {
74    private static final long serialVersionUID = 1L;
75  
76    public MelatiShoppingConfig config;
77     
78    /**
79     * Inititialise the Shopping Trolley Engine.  This will load a file called
80     * org.paneris.melati.shopping.ShoppingTrolley.properties in order to 
81     * find the classes that implement this shopping implementation.
82     *
83     * @param conf - the Servlet's config parameters
84     * @see org.paneris.melati.shopping.MelatiShoppingConfig
85     **/
86    public void init(ServletConfig conf) throws ServletException {
87      super.init(conf);
88      try {
89        config = new MelatiShoppingConfig();
90      } catch (MelatiException e) {
91        throw new ServletException(e.toString());
92      }
93    }
94  
95    /**
96     * Main entry point for this servlet.
97     *
98     * @param melati - the melati for this request
99     * @param context - the Template Context for this request
100    *
101    * @return - the name of the template to be returned to the user
102    *
103    * @throws InvalidUsageException - if this request has an invalid form
104    */
105   protected String 
106       doTemplateRequest(Melati melati, ServletTemplateContext context)
107       throws Exception {
108 
109     if (config==null) 
110        throw new ShoppingConfigException("Shopping Trolley not Configured");
111     // at any point, the user can be forced to login, 
112     // by simply appending "?login"
113     // to the url
114     if (Form.getFormNulled(context,"login") != null) assertLogin(melati);
115     ShoppingContext shoppingContext = (ShoppingContext)melati.getPoemContext();
116     if (shoppingContext.getMethod().equals("Load")) 
117       return Load(melati, shoppingContext.stid);
118     if (shoppingContext.getMethod().equals("View")) return View(melati);
119     if (shoppingContext.getMethod().equals("Update")) return Update(melati);
120     if (shoppingContext.getMethod().equals("Add")) 
121       return Add(melati, shoppingContext.stid, shoppingContext.quantity);
122     if (shoppingContext.getMethod().equals("MultipleAdd")) 
123       return MultipleAdd(melati);
124     if (shoppingContext.getMethod().equals("Remove")) 
125       return Remove(melati, shoppingContext.stid);
126     if (shoppingContext.getMethod().equals("Set")) 
127       return Set(melati, shoppingContext.stid, shoppingContext.quantity);
128     if (shoppingContext.getMethod().equals("Details")) return Details(melati);
129     if (shoppingContext.getMethod().equals("Confirm")) return Confirm(melati);
130     if (shoppingContext.getMethod().equals("Paid")) return Paid(melati);
131     if (shoppingContext.getMethod().equals("Abandon")) return Abandon(melati);
132     throw new InvalidUsageException(this, shoppingContext);
133   }
134 
135   /** 
136    * Load the trolley from something persistent.
137    *
138    * @param melati - the melati for this request
139    * @param id - an id that can be used to identify the trolley to be loaded
140    *
141    * @return - "Trolley" - the page where users manipulate their 
142    *           Shopping Trolley
143    *
144    * @throws InstantiationPropertyException - if we cannot construct trolley
145    */
146   protected String Load(Melati melati, Integer id)
147       throws InstantiationPropertyException {
148     ShoppingTrolley trolley = ShoppingTrolley.newTrolley(config);
149     trolley.initialise(melati,config,id);
150     melati.getTemplateContext().put("trolley", trolley);
151     return shoppingTemplate(melati, "Trolley");
152   }
153 
154 
155   /** 
156    * Load the trolley from something persistent.
157    *
158    * @param melati - the melati for this request
159    *
160    * @return - "Trolley" - the page where users manipulate their 
161    *           Shopping Trolley
162    *
163    * @throws InstantiationPropertyException - if we cannot construct trolley
164    **/
165   protected String Save(Melati melati)
166       throws InstantiationPropertyException {
167     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
168     trolley.save();
169     melati.getTemplateContext().put("trolley", trolley);
170     return shoppingTemplate(melati, "Trolley");
171   }
172 
173   /** 
174    * View the trolley.
175    *
176    * @param melati - the melati for this request
177    *
178    * @return - "Trolley" - the page where users manipulate their 
179    *           Shopping Trolley
180    *
181    * @throws InstantiationPropertyException if we cannot construct the trolley
182    */
183   protected String View(Melati melati)
184       throws InstantiationPropertyException {
185     melati.getTemplateContext().put("trolley", 
186         ShoppingTrolley.getInstance(melati,config));
187     return shoppingTemplate(melati, "Trolley");
188   }
189 
190   /** 
191    * Update the entire trolley, changing quantities
192    * and removing items.  The POSTed form is analysed for fields with names of 
193    * the form:
194    *
195    * trolleyitem_<item id>_quantity - the new quantity of the item (if set)
196    * trolleyitem_<item id>_deleted - remove this item from the trolley (if set)
197    *
198    * items will also be deleted if the quantity is set to 0
199    *
200    * @param melati - the melati for this request
201    *
202    * @return - "Trolley" - the page where users manipulate their 
203    *           Shopping Trolley
204    *
205    * @throws InstantiationPropertyException if we cannot construct the trolley
206    */
207   protected String Update(Melati melati) 
208       throws InstantiationPropertyException {
209     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
210     for (Enumeration c = trolley.getItems(); c.hasMoreElements();) {
211       ShoppingTrolleyItem item = (ShoppingTrolleyItem)c.nextElement();
212       String formName = "trolleyitem_" + item.getId();
213       String formQuantity = formName + "_quantity";
214       String formDeleted = formName + "_deleted";
215       String deleted = 
216              Form.getFormNulled(melati.getServletTemplateContext(),
217                                       formDeleted);
218       String quantity = 
219              Form.getFormNulled(melati.getServletTemplateContext(),
220                                       formQuantity);
221       System.err.println(deleted + " " + quantity);
222       if (deleted != null || quantity == null || quantity.equals("0")) {
223         trolley.removeItem(item);
224       } else {
225         item.setQuantity(new Double(quantity).doubleValue());
226       }
227     }
228     melati.getTemplateContext().put("trolley",trolley);
229     return shoppingTemplate(melati, "Trolley");
230   }
231 
232   /** 
233    * Add multiple items to the trolley, 
234    * or add to the quantities already in the 
235    * trolley.  
236    * The POSTed form is analysed for fields with names of 
237    * the form:
238    *
239    * product_<item id> - the id of the item to be added
240    * quantity_<item id> - the quantity to add
241    *
242    * If no quantity is set, a single item will be added.
243    *
244    * @param melati - the melati for this request
245    *
246    * @return - "Trolley" - the page where users manipulate their 
247    *           Shopping Trolley
248    *
249    * @throws InstantiationPropertyException - if we cannot construct trolley
250    */
251   protected String MultipleAdd(Melati melati)
252       throws InstantiationPropertyException {
253     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
254     for (Enumeration e = melati.getRequest().getParameterNames(); 
255                      e.hasMoreElements();) {
256       String name = (String)e.nextElement();
257       if (name.length() > 8) {
258         String p = name.substring(0,7);
259         if (p.equals("product")) {
260           String id = name.substring(8);
261           Integer idInt = new Integer(id);
262           ShoppingTrolleyItem item = trolley.getItem(idInt);
263           String quantityName = "quantity_" + id;
264           String priceName = "price_" + id;
265           String descriptionName = "description_" + id;
266           double quantity = 1;
267           Double price = null;
268           String quantitySring = 
269                  Form.getFormNulled
270                  (melati.getServletTemplateContext(), quantityName);
271           String priceString = 
272                  Form.getFormNulled
273                  (melati.getServletTemplateContext(), priceName);
274           String description = 
275                  Form.getFormNulled
276                  (melati.getServletTemplateContext(), descriptionName);
277           if (quantitySring != null) 
278             quantity = (new Double(quantitySring)).doubleValue();
279           if (priceString != null) price = new Double(priceString);
280           if (item == null) {
281             item = newItem(trolley,idInt,price,description);
282           }
283           item.setQuantity(item.getQuantity() + quantity);
284         }
285       }
286     }
287     melati.getTemplateContext().put("trolley",trolley);
288     return shoppingTemplate(melati, "Trolley");
289   }
290 
291   /** 
292    * Add a single item to the trolley, or add to the quantity already in the 
293    * trolley.  The product is specified on the pathinfo which should be of the
294    * form:
295    *
296    * /<logicaldatabase>/<id>/<quantity>/Add/
297    *
298    * if no quantity is set, a single item will be added.  The form parmaeters
299    * will be parsed to see if they contain "price" and/or "description" fields.
300    * if they are present, they will be used to set up the item
301    *
302    * @param melati - the melati for this request
303    * @param id - the id of the item to be added
304    *
305    * @return - "Trolley" - the page where users manipulate their 
306    *           Shopping Trolley
307    *
308    * @throws InstantiationPropertyException - if we cannot construct trolley
309    **/
310   protected String Add(Melati melati, Integer id, double quantity)
311       throws InstantiationPropertyException {
312      System.err.println("Adding");
313     // the quantity is defaulted to 1, so if you don't set it you will get one
314     if (quantity == 0) quantity = 1;
315     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
316     ShoppingTrolleyItem item = trolley.getItem(id);
317     if (item == null) {
318       Double price = null;
319       String priceString = 
320              Form.getFormNulled(melati.getServletTemplateContext(), 
321                                       "price");
322       if (priceString != null) price = new Double(priceString);
323       item = newItem(trolley,id,price,
324           Form.getFormNulled(melati.getServletTemplateContext(), 
325                                    "description"));
326     }    
327     item.setQuantity(item.getQuantity() + quantity);
328     melati.getTemplateContext().put("trolley",trolley);
329     return shoppingTemplate(melati, "Trolley");
330   }
331 
332   /** 
333    * Remove a single item from the trolley, the product is specified on the 
334    * pathinfo which should be of the form:
335    *
336    * /<logicaldatabase>/<id>/Remove/
337    *
338    * @param melati - the melati for this request
339    *
340    * @return - "Trolley" - the page where users manipulate their 
341    *           Shopping Trolley
342    *
343    * @throws InstantiationPropertyException - if we cannot construct trolley
344    */
345   protected String Remove(Melati melati, Integer id)
346       throws InstantiationPropertyException {
347     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati, config);
348     ShoppingTrolleyItem item = trolley.getItem(id);
349     trolley.removeItem(item);
350     melati.getTemplateContext().put("trolley", trolley);
351     return shoppingTemplate(melati, "Trolley");
352   }
353 
354   /** 
355    * Set the quantity of an item in the trolley, 
356    * the product and new quantity is
357    * specified on the pathinfo which should be of the form:
358    *
359    * /<logicaldatabase>/<id>/<quantity>/Set/
360    *
361    * @param melati - the melati for this request
362    * @param id - the id of the item to be removed
363    *
364    * @return - "Trolley" - the page where users manipulate their 
365    *           Shopping Trolley
366    *
367    * @throws InstantiationPropertyException - if we cannot construct trolley
368    */
369   protected String Set(Melati melati, Integer id, double quantity)
370       throws InstantiationPropertyException {
371     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
372     ShoppingTrolleyItem item = trolley.getItem(id);
373     if (item == null) item = newItem(trolley,id, null, null);
374     item.setQuantity(quantity);
375     melati.getTemplateContext().put("trolley",trolley);
376     return shoppingTemplate(melati, "Trolley");
377   }
378   
379   /** 
380    * Return the page where the user enters their details.
381    *
382    * @param melati - the melati for this request
383    *
384    * @return - "Details" - the page where users enter their details
385    *
386    * @throws InstantiationPropertyException - if we cannot construct trolley
387    */
388   protected String Details(Melati melati)
389       throws InstantiationPropertyException {
390     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
391     trolley.setDefaultDetails(melati);
392     melati.getTemplateContext().put("trolley",trolley);
393     return shoppingTemplate(melati, "Details");
394   }
395   
396   /** 
397    * Update the user's information and return the
398    * confirmation page.
399    *
400    * @param melati - the melati for this request
401    *
402    * @return - "Confirm" - the page where users confirm their order
403    *
404    * @throws InstantiationPropertyException - if we cannot construct trolley
405    */
406   protected String Confirm(Melati melati)
407       throws InstantiationPropertyException {
408     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati,config);
409     if (Form.getFormNulled(melati.getServletTemplateContext(),
410                                  "submittoken") != null) 
411       trolley.setFromForm(melati);
412     trolley.save();
413     melati.getTemplateContext().put("trolley",trolley);
414     return shoppingTemplate(melati, "Confirm");
415   }
416   
417   /** 
418    * Complete the user's shopping experience, and remove their Shopping Trolley
419    * from the Session.
420    *
421    * If you need to do something (like send an email) following confirmation 
422    * of payment, define the method in <Your>ShoppingTrolley.java:
423    * 
424    *  public void confirmPayment(Melati melati) {}
425    *
426    * Because the callback request (typically) comes from the Payment Server, 
427    * you will not have the user's shoping trolley (Session) available to them.
428    * You will therefore need to get whatever information you require from 
429    * something persistent.
430    *
431    * The alternative is to get the Payment Server to generate the emails (or 
432    * whatever) for you.  Most Payment Servers offer this facility. 
433    *
434    * @param melati - the melati for this request
435    *
436    * @return - "Paid" - a message thanking the user for their order
437    *
438    * @throws InstantiationPropertyException - if we cannot construct trolley
439    */
440   protected String Paid(Melati melati) 
441       throws InstantiationPropertyException {
442     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati, config);
443     trolley.confirmPayment(melati);
444     // and get rid of it
445     trolley.remove(melati);
446     return shoppingTemplate(melati, "Paid");
447   }
448   
449   /** 
450    * Abandon a trolley. 
451    *
452    * @param melati - the melati for this request
453    *
454    * @return - "Trolley" - the initial trolley page
455    *
456    * @throws InstantiationPropertyException - if we cannot construct trolley
457    */
458   protected String Abandon(Melati melati) 
459       throws InstantiationPropertyException {
460     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati, config);
461     // and get rid of it
462     trolley.remove(melati);
463     return shoppingTemplate(melati, "Trolley");
464   }
465   
466   /** 
467    * Force a user to login.
468    *
469    * @param melati - the melati for this request
470    */
471   protected void assertLogin(Melati melati) 
472       throws InstantiationPropertyException {
473     ShoppingTrolley trolley = ShoppingTrolley.getInstance(melati, config);
474     // deligate to your trolley
475     trolley.assertLogin(melati);
476   }
477 
478   /** 
479    * Create the full name of the template to be returned.
480    *
481    * @param melati - the melati for this request (not used)
482    * @param name - the name of the template
483    *
484    * @return - the full path to the template
485    */
486   protected String shoppingTemplate(Melati melati, String name) {
487     return "shopping/" + name;
488   }
489 
490   /** 
491    * Create a new item and add it to the ShoppingTrolley 
492    * if a non null price is passed in.
493    *
494    * @param trolley - the trolley to add the item to 
495    * @param id - the id of the item to be added
496    *
497    * @return - the new shopping trolley item
498    */
499   private ShoppingTrolleyItem newItem(ShoppingTrolley trolley, Integer id,
500                                       Double price, String description)
501       throws InstantiationPropertyException {
502     return trolley.newItem(id, description, price);
503   }
504 
505   /** 
506    * Override the building of the PoemContext in order to glean the 
507    * additional information required for the Shopping Trolley system.
508    *
509    * @param melati - the melati for this request
510    *
511    * @return - the ShoppingContext with as many bits set up as possible
512    *
513    * @throws PathInfoException - if we don't understand the PathInfo
514    */
515   protected PoemContext poemContext(Melati melati)
516       throws PathInfoException {
517     ShoppingContext it = new ShoppingContext();
518     String[] parts = melati.getPathInfoParts();
519     if (parts.length < 2) 
520       throw new PathInfoException(
521           "The servlet expects to see pathinfo in the form " +
522           "/db/method/ or /db/method/troid or /db/method/troid/quantity");
523     it.setLogicalDatabase(parts[0]);
524     it.setMethod(parts[1]);
525     try {
526       if (parts.length > 2 && !parts[2].equals("")) 
527         it.stid = new Integer(parts[2]);
528       if (parts.length > 3 && !parts[3].equals(""))
529         it.quantity = (new Double(parts[3])).doubleValue();
530     } catch (NumberFormatException e) {
531       throw new PathInfoException(
532           "The servlet expects to see pathinfo in the form " +
533           "/db/method/ or /db/troid/method/ or /db/troid/quantity/method/ " +
534           "where the troid is an integer and the quantity is a number");
535     }
536     return it;
537   }
538 
539 }