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