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 }