View Javadoc

1   /*
2    * Copyright (c) 2001 - 2005 ivata limited.
3    * All rights reserved.
4    * -----------------------------------------------------------------------------
5    * ivata masks may be redistributed under the GNU General Public
6    * License as published by the Free Software Foundation;
7    * version 2 of the License.
8    *
9    * These programs are free software; you can redistribute them and/or
10   * modify them under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; version 2 of the License.
12   *
13   * These programs are distributed in the hope that they will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16   *
17   * See the GNU General Public License in the file LICENSE.txt for more
18   * details.
19   *
20   * If you would like a copy of the GNU General Public License write to
21   *
22   * Free Software Foundation, Inc.
23   * 59 Temple Place - Suite 330
24   * Boston, MA 02111-1307, USA.
25   *
26   *
27   * To arrange commercial support and licensing, contact ivata at
28   *                  http://www.ivata.com/contact.jsp
29   * -----------------------------------------------------------------------------
30   * $Log: Catalog.java,v $
31   * Revision 1.13  2005/04/27 17:23:32  colinmacleod
32   * Fixed bugs resulting from ivata masks changes
33   * for ivata groupware v0.11.
34   *
35   * Revision 1.12  2005/04/11 14:05:57  colinmacleod
36   * Added preliminary support for filters.
37   * Added FieldValueConvertor factor interface
38   * to split off value convertors for reuse.
39   *
40   * Revision 1.11  2005/04/09 18:04:13  colinmacleod
41   * Changed copyright text to GPL v2 explicitly.
42   *
43   * Revision 1.10  2005/01/19 12:15:42  colinmacleod
44   * Added new methods for system-specific session.
45   * (Required by ivata groupware 0.10.)
46   *
47   * Revision 1.9  2005/01/11 10:08:50  colinmacleod
48   * Removed trailing spaces.
49   *
50   * Revision 1.8  2005/01/10 18:59:55  colinmacleod
51   * Improved remove routine by using persistence
52   * manager features unavailable in the first release.
53   *
54   * Revision 1.7  2005/01/07 09:13:02  colinmacleod
55   * Added newline to end of file.
56   *
57   * Revision 1.6  2005/01/07 08:08:17  colinmacleod
58   * Moved up a version number.
59   * Changed copyright notices to 2005.
60   * Updated the documentation:
61   *   - started working on multiproject:site docu.
62   *   - changed the logo.
63   * Added checkstyle and fixed LOADS of style issues.
64   * Added separate thirdparty subproject.
65   * Added struts (in web), util and webgui (in webtheme) from ivata op.
66   *
67   * Revision 1.5  2004/12/29 09:56:31  colinmacleod
68   * Corrected project name in comment.
69   *
70   * Revision 1.4  2004/11/12 15:10:40  colinmacleod
71   * Moved persistence classes from ivata op as a replacement for
72   ValueObjectLocator.
73   *
74   * Revision 1.3  2004/11/10 17:19:22  colinmacleod
75   * Added handling for new value object classes.
76   *
77   * Revision 1.2  2004/05/19 20:32:31  colinmacleod
78   * Added methods to add, amend and remove value objects.
79   *
80   * Revision 1.1.1.1  2004/05/16 20:40:07  colinmacleod
81   * Ready for 0.1 release
82   * -----------------------------------------------------------------------------
83   */
84  package com.ivata.mask.web.demo.catalog;
85  import java.beans.PropertyDescriptor;
86  import java.io.Serializable;
87  import java.util.HashMap;
88  import java.util.Iterator;
89  import java.util.List;
90  import java.util.Map;
91  import java.util.Set;
92  import java.util.Vector;
93  
94  import org.apache.commons.beanutils.PropertyUtils;
95  import org.apache.log4j.Logger;
96  
97  import com.ivata.mask.DefaultMaskFactory;
98  import com.ivata.mask.MaskFactory;
99  import com.ivata.mask.field.DefaultFieldValueConvertorFactory;
100 import com.ivata.mask.persistence.PersistenceException;
101 import com.ivata.mask.persistence.PersistenceManager;
102 import com.ivata.mask.persistence.PersistenceSession;
103 import com.ivata.mask.persistence.right.DefaultPersistenceRights;
104 import com.ivata.mask.persistence.right.PersistenceRights;
105 import com.ivata.mask.valueobject.ValueObject;
106 import com.ivata.mask.web.demo.customer.CustomerDO;
107 import com.ivata.mask.web.demo.order.OrderDO;
108 import com.ivata.mask.web.demo.order.item.OrderItemDO;
109 import com.ivata.mask.web.demo.product.ProductDO;
110 import com.ivata.mask.web.demo.valueobject.DemoValueObject;
111 import com.ivata.mask.web.struts.ValueObjectException;
112 /***
113  * <p>
114  * This is the main class of the demo. It represents a catalog of products.
115  * </p>
116  *
117  * @author Colin MacLeod
118  * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
119  * @since ivata masks 0.1 (2004-05-09)
120  * @version $Revision: 1.13 $
121  */
122 public final class Catalog implements PersistenceManager {
123     /***
124      * Unique id for invalid objects.
125      */
126     public static final int INVALID_ID = -1;
127     /***
128      * Refer to {@link Logger}.
129      */
130     private static Logger log = Logger.getLogger(Catalog.class);
131     /***
132      * <p>
133      * For the demo, this is a singleton. It's better to use dependency
134      * injection or service locator patterns, in real life.
135      * </p>
136      */
137     private static Catalog theInstance = null;
138     /***
139      * <p>
140      * Get the sole instance of this class.
141      * </p>
142      *
143      * <p>
144      * For the demo, this is a singleton. It's better to use dependency
145      * injection or service locator patterns, in real life.
146      * </p>
147      *
148      * @return only instance of this class.
149      */
150     public static synchronized Catalog getInstance() {
151         if (theInstance == null) {
152             theInstance = new Catalog();
153         }
154         return theInstance;
155     }
156     /***
157      * <p>
158      * Map of integer instances - counters for the ids of each class. Referenced
159      * by class name.
160      * </p>
161      */
162     private Map counters = new HashMap();
163     /***
164      * <p>
165      * Stores all the instances, referenced by class, then id string.
166      * </p>
167      */
168     private Map instances = new HashMap();
169     /***
170      * <p>
171      * Stores the mask factory used throughout this application.
172      * </p>
173      * <p>
174      * <b>Note </b> we have overridden the name of the list mask used by default
175      * to just &quot;list&quot; - you could do the same for the default input
176      * mask. (This dictates the name of the <strong>Struts </strong> action
177      * class used).
178      * </p>
179      */
180     private MaskFactory maskFactory = new DefaultMaskFactory("inputMask",
181             "list",
182             new DefaultFieldValueConvertorFactory());
183     /***
184      * <p>
185      * Private default constructor enforces singleton behavior.
186      * </p>
187      */
188     private Catalog() {
189         reset();
190         initializeDO(CustomerDO.class);
191         initializeDO(OrderDO.class);
192         initializeDO(OrderItemDO.class);
193         initializeDO(ProductDO.class);
194     }
195     /***
196      * <p>
197      * Add a new object to be persisted.
198      * </p>
199      *
200      * @param session open persistence session, used to store the object.
201      * @param valueObject
202      *            value object to be persisted.
203      * @return new value object with id set appropriately.
204      * @throws PersistenceException thrown by the underlying persistence
205      * mechanism if the object cannot be saved for any reason.
206      * @see com.ivata.mask.persistence.PersistenceManager#add
207      */
208     public synchronized ValueObject add(
209             final PersistenceSession session,
210             final ValueObject valueObject) throws PersistenceException {
211         String className = valueObject.getClass().getName();
212         Integer counter = (Integer) counters.get(className);
213         Map instancesThisClass = (Map) instances.get(className);
214         if (counter == null) {
215             throw new NullPointerException(
216                     "ERROR: unknown value object class '"
217                             + valueObject.getClass() + "'");
218         }
219         counter = new Integer(counter.intValue() + 1);
220         counters.put(className, counter);
221         DemoValueObject demoValueObject = (DemoValueObject) valueObject;
222         demoValueObject.setId(counter.intValue());
223         instancesThisClass.put(demoValueObject.getIdString(), demoValueObject);
224         return demoValueObject;
225     }
226     /***
227      * <p>
228      * Persist changes to an existing object.
229      * </p>
230      *
231      * @param session open persistence session, used to store the changes to
232      * the object.
233      * @param valueObject value object to be persisted.
234      * @throws PersistenceException thrown by the underlying persistence
235      * mechanism if the object cannot be saved for any reason.
236      * @see com.ivata.mask.persistence.PersistenceManager#amend
237      */
238     public synchronized void amend(final PersistenceSession session,
239             final ValueObject valueObject) throws PersistenceException {
240         DemoValueObject demoValueObject = (DemoValueObject) valueObject;
241         String className = valueObject.getClass().getName();
242         Map instancesThisClass = (Map) instances.get(className);
243         if (instancesThisClass == null) {
244             throw new NullPointerException(
245                     "ERROR: unknown value object class '"
246                             + valueObject.getClass() + "'");
247         }
248         ValueObject existingObject = (ValueObject) instancesThisClass
249             .get(demoValueObject.getIdString());
250         setPropertyValues(demoValueObject, existingObject);
251     }
252     /***
253      * Retrieve all instances of a particular class. This method is used in
254      * the list pages.
255      *
256      * @param session open persistence session, used to fetch the objects.
257      * @param classParam class of objects to be retrieved. Must be a subclass
258      * of <code>ValueObject</code>.
259      * @return <code>List</code> containing instances of the requested class.
260      * @see com.ivata.mask.PersistenceManager#locateByBaseClass
261      */
262     public List findAll(final PersistenceSession session,
263             final Class classParam) {
264         Map instancesThisClass = getInstanceMap(classParam);
265         return new Vector(instancesThisClass.values());
266     }
267     /***
268      * Retrieve a single instance of a particular class. This method is used in
269      * the input/display pages.
270      *
271      * @param session open persistence session, used to fetch the object.
272      * @param classParam class of object to be retrieved. Must be a subclass
273      * of <code>ValueObject</code>.
274      * @param key unique identifier of the object to retrieve.
275      * @return instance of the requested class which matches the identifier
276      * <code>key</code>.
277      * @throws PersistenceException thrown by the underlying persistence
278      * mechanism if the object cannot be retrieved for any reason.
279      * @see com.ivata.mask.persistence.PersistenceManager#findByPrimaryKey
280      */
281     public ValueObject findByPrimaryKey(final PersistenceSession session,
282             final Class classParam,
283             final Serializable key) throws PersistenceException {
284         Map instancesThisClass = getInstanceMap(classParam);
285         return (ValueObject) instancesThisClass.get(key);
286     }
287     /***
288      * Class instances are stored internally within a map, indexed by the
289      * object's identifier. This private helper retrieves the map for a given
290      * class.
291      *
292      * @param classParam indicates the class of value objects which should be
293      * indexed by the map
294      * @return a map of all value objects matching the requested class.
295      */
296     private Map getInstanceMap(final Class classParam) {
297         Map instancesThisClass = null;
298         Class baseClass = classParam;
299         while (instancesThisClass == null) {
300             if ((baseClass == null)
301                     || "java.lang.Object".equals(baseClass.getName())) {
302                 throw new RuntimeException("ERROR in value object locator: no "
303                         + "instances for class '" + classParam.getName() + "'");
304             }
305             instancesThisClass = (Map) instances.get(baseClass.getName());
306             baseClass = baseClass.getSuperclass();
307         }
308         return instancesThisClass;
309     }
310 
311     /***
312      * <p>
313      * Get the mask factory used throughout this application. This is used to
314      * create and retrieve input/list masks.
315      * </p>
316      *
317      * @return mask factory used throughout this application.
318      */
319     public synchronized MaskFactory getMaskFactory() {
320         return maskFactory;
321     }
322 
323     /***
324      * Just returns an instance of the default rights implementation. This
325      * lets you do everything :-)
326      *
327      * @return Instance of {@link DefaultPersistenceRights}.
328      */
329     public PersistenceRights getPersistenceRights() {
330         return new DefaultPersistenceRights();
331     }
332     /***
333      * <p>
334      * Initialize the map entries for a particular value object. All value
335      * objects are stored in maps - this creates the map for value objects of a
336      * single class.
337      * </p>
338      *
339      * @param valueObjectClass class of value object whose internal storage
340      * map should be prepared.
341      */
342     private void initializeDO(final Class valueObjectClass) {
343         String className = valueObjectClass.getName();
344         counters.put(className, new Integer(0));
345         instances.put(className, new HashMap());
346     }
347     /***
348      * <p>
349      * Open a catalog session.
350      * </p>
351      *
352      * @see com.ivata.mask.persistence.PersistenceManager#openSession()
353      */
354     public PersistenceSession openSession() throws PersistenceException {
355         return new CatalogSession();
356     }
357     /***
358      * You can't use a system session in this simple demo. This method throws
359      * an exception of class <code>UnsupportedOperationException</code>.
360      *
361      * @param systemSession
362      * @return
363      * @throws PersistenceException
364      * @see com.ivata.mask.persistence.PersistenceManager#openSession
365      */
366     public PersistenceSession openSession(final Object systemSession)
367             throws PersistenceException {
368         // NOTE: the demo application ignores the system session!
369         return openSession();
370     }
371     /***
372      * <p>
373      * Remove a value object from the system.
374      * </p>
375      *
376      * @param session open persistence session, used to remove the object.
377      * @param classParam class of object to be retrieved. Must be a subclass
378      * of <code>ValueObject</code>.
379      * @param key unique identifier of the object to retrieve.
380      * @throws PersistenceException thrown by the underlying persistence
381      * mechanism if the object cannot be removed for any reason.
382      * @see com.ivata.mask.persistence.PersistenceManager#remove
383      */
384     public void remove(final PersistenceSession session,
385             final Class classParam,
386             final Serializable key) throws PersistenceException {
387         assert (classParam != null);
388         assert (key != null);
389 
390         Map instancesThisClass = (Map) instances.get(classParam.getName());
391         instancesThisClass.remove(key);
392         // also have to cascade changes to order/order item - your perswistence
393         // layer (such as hibernate) should take care of this stuff, in real
394         // life
395         if ("com.ivata.mask.web.demo.customer.CustomerDO".equals(classParam
396                 .getName())) {
397             Map orderInstances = (Map) instances
398                     .get("com.ivata.mask.web.demo.order.OrderDO");
399             Iterator keyIterator = orderInstances.keySet().iterator();
400             while (keyIterator.hasNext()) {
401                 String orderId = (String) keyIterator.next();
402                 OrderDO order = (OrderDO) orderInstances.get(orderId);
403                 if ((order.getCustomer() != null)
404                         && key.equals(order.getCustomer().getIdString())) {
405                     order.setCustomer(null);
406                 }
407             }
408         } else if ("com.ivata.mask.web.demo.product.ProductDO".equals(classParam
409                 .getName())) {
410             Map orderItemInstances = (Map) instances
411                     .get("com.ivata.mask.web.demo.order.item.OrderItemDO");
412             Iterator keyIterator = orderItemInstances.keySet().iterator();
413             while (keyIterator.hasNext()) {
414                 String orderItemId = (String) keyIterator.next();
415                 OrderItemDO orderItem = (OrderItemDO) orderItemInstances
416                         .get(orderItemId);
417                 if ((orderItem.getProduct() != null)
418                         && key.equals(orderItem.getProduct().getIdString())) {
419                     orderItem.setProduct(null);
420                 }
421             }
422         }
423     }
424     /***
425      * <p>
426      * Reset the catalog to its initial state.
427      * </p>
428      */
429     public void reset() {
430         // go thro' all the classes and reset...
431         Set keySet = instances.keySet();
432         for (Iterator keyIterator = keySet.iterator(); keyIterator.hasNext();) {
433             String className = (String) keyIterator.next();
434             Map instancesThisClass = (Map) instances.get(className);
435             instancesThisClass.clear();
436             counters.put(className, new Integer(0));
437         }
438     }
439     /***
440      * <p>
441      * Set the mask factory used throughout this application.
442      * </p>
443      *
444      * @param factory
445      *            mask factory used throughout this application.
446      */
447     public void setMaskFactory(final MaskFactory factory) {
448         maskFactory = factory;
449     }
450 
451     /***
452      * <p>
453      * Override to set all property values from one value object to another.
454      * </p>
455      *
456      * @param from
457      *            value object to take values from.
458      * @param to
459      *            value object to set values in.
460      */
461     protected void setPropertyValues(final ValueObject from,
462             final ValueObject to) {
463         // go thro' and set all properties
464         PropertyDescriptor[] descriptors = PropertyUtils
465                 .getPropertyDescriptors(to.getClass());
466         for (int i = 0; i < descriptors.length; i++) {
467             PropertyDescriptor descriptor = descriptors[i];
468             String propertyName = descriptor.getName();
469             try {
470                 PropertyUtils.setProperty(to, propertyName, PropertyUtils
471                         .getProperty(from, propertyName));
472             } catch (NoSuchMethodException e) {
473                 // this is probably ok
474                 if (!("idString".equals(propertyName) || "class"
475                         .equals(propertyName))) {
476                     log.warn("Warning (" + e.getClass().getName()
477                             + "): no setter method for property '"
478                             + propertyName + "' on value object '" + to + "'");
479                 }
480             } catch (Exception e) {
481                 try {
482                     throw new ValueObjectException(e, to.getClass(),
483                             "setting property called '" + propertyName + "'");
484                 } catch (ValueObjectException e1) {
485                     // TODO Auto-generated catch block
486                     e1.printStackTrace();
487                 }
488             }
489         }
490     }
491 }
492