001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata masks may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: Catalog.java,v $
031: * Revision 1.17 2005/10/12 18:37:13 colinmacleod
032: * Standardized format of Logger declaration - to make it easier to find instances
033: * which are not both static and final.
034: *
035: * Revision 1.16 2005/10/03 10:17:24 colinmacleod
036: * Fixed some style and javadoc issues.
037: *
038: * Revision 1.15 2005/10/02 14:06:31 colinmacleod
039: * Added/improved log4j logging.
040: *
041: * Revision 1.14 2005/09/29 12:08:02 colinmacleod
042: * Removed PersistenceRights (for now, moved to ivata groupware).
043: * Changed openSession method in PersistenceManager to accept HTTP
044: * session.
045: *
046: * Revision 1.13 2005/04/27 17:23:32 colinmacleod
047: * Fixed bugs resulting from ivata masks changes
048: * for ivata groupware v0.11.
049: *
050: * Revision 1.12 2005/04/11 14:05:57 colinmacleod
051: * Added preliminary support for filters.
052: * Added FieldValueConvertor factor interface
053: * to split off value convertors for reuse.
054: *
055: * Revision 1.11 2005/04/09 18:04:13 colinmacleod
056: * Changed copyright text to GPL v2 explicitly.
057: *
058: * Revision 1.10 2005/01/19 12:15:42 colinmacleod
059: * Added new methods for system-specific session.
060: * (Required by ivata groupware 0.10.)
061: *
062: * Revision 1.9 2005/01/11 10:08:50 colinmacleod
063: * Removed trailing spaces.
064: *
065: * Revision 1.8 2005/01/10 18:59:55 colinmacleod
066: * Improved remove routine by using persistence
067: * manager features unavailable in the first release.
068: *
069: * Revision 1.7 2005/01/07 09:13:02 colinmacleod
070: * Added newline to end of file.
071: *
072: * Revision 1.6 2005/01/07 08:08:17 colinmacleod
073: * Moved up a version number.
074: * Changed copyright notices to 2005.
075: * Updated the documentation:
076: * - started working on multiproject:site docu.
077: * - changed the logo.
078: * Added checkstyle and fixed LOADS of style issues.
079: * Added separate thirdparty subproject.
080: * Added struts (in web), util and webgui (in webtheme) from ivata op.
081: *
082: * Revision 1.5 2004/12/29 09:56:31 colinmacleod
083: * Corrected project name in comment.
084: *
085: * Revision 1.4 2004/11/12 15:10:40 colinmacleod
086: * Moved persistence classes from ivata op as a replacement for
087: ValueObjectLocator.
088: *
089: * Revision 1.3 2004/11/10 17:19:22 colinmacleod
090: * Added handling for new value object classes.
091: *
092: * Revision 1.2 2004/05/19 20:32:31 colinmacleod
093: * Added methods to add, amend and remove value objects.
094: *
095: * Revision 1.1.1.1 2004/05/16 20:40:07 colinmacleod
096: * Ready for 0.1 release
097: * -----------------------------------------------------------------------------
098: */
099: package com.ivata.mask.web.demo.catalog;
100:
101: import java.beans.PropertyDescriptor;
102: import java.io.Serializable;
103: import java.util.HashMap;
104: import java.util.Iterator;
105: import java.util.List;
106: import java.util.Map;
107: import java.util.Set;
108: import java.util.Vector;
109:
110: import javax.servlet.http.HttpSession;
111:
112: import org.apache.commons.beanutils.PropertyUtils;
113: import org.apache.log4j.Logger;
114:
115: import com.ivata.mask.DefaultMaskFactory;
116: import com.ivata.mask.MaskFactory;
117: import com.ivata.mask.field.DefaultFieldValueConvertorFactory;
118: import com.ivata.mask.persistence.PersistenceException;
119: import com.ivata.mask.persistence.PersistenceManager;
120: import com.ivata.mask.persistence.PersistenceSession;
121: import com.ivata.mask.valueobject.ValueObject;
122: import com.ivata.mask.web.demo.customer.CustomerDO;
123: import com.ivata.mask.web.demo.order.OrderDO;
124: import com.ivata.mask.web.demo.order.item.OrderItemDO;
125: import com.ivata.mask.web.demo.product.ProductDO;
126: import com.ivata.mask.web.demo.valueobject.DemoValueObject;
127: import com.ivata.mask.web.struts.ValueObjectException;
128:
129: /**
130: * <p>
131: * This is the main class of the demo. It represents a catalog of products.
132: * </p>
133: *
134: * @author Colin MacLeod
135: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
136: * @since ivata masks 0.1 (2004-05-09)
137: * @version $Revision: 1.17 $
138: */
139: public final class Catalog implements PersistenceManager {
140: /**
141: * Unique id for invalid objects.
142: */
143: public static final int INVALID_ID = -1;
144: /**
145: * Logger for this class.
146: */
147: private static final Logger logger = Logger
148: .getLogger(Catalog.class);
149: /**
150: * <p>
151: * For the demo, this is a singleton. It's better to use dependency
152: * injection or service locator patterns, in real life.
153: * </p>
154: */
155: private static Catalog theInstance = null;
156:
157: /**
158: * <p>
159: * Get the sole instance of this class.
160: * </p>
161: *
162: * <p>
163: * For the demo, this is a singleton. It's better to use dependency
164: * injection or service locator patterns, in real life.
165: * </p>
166: *
167: * @return only instance of this class.
168: */
169: public static synchronized Catalog getInstance() {
170: if (logger.isDebugEnabled()) {
171: logger.debug("getInstance() - start");
172: }
173:
174: if (theInstance == null) {
175: theInstance = new Catalog();
176: }
177:
178: if (logger.isDebugEnabled()) {
179: logger.debug("getInstance() - end - return value = "
180: + theInstance);
181: }
182: return theInstance;
183: }
184:
185: /**
186: * <p>
187: * Map of integer instances - counters for the ids of each class. Referenced
188: * by class name.
189: * </p>
190: */
191: private Map counters = new HashMap();
192: /**
193: * <p>
194: * Stores all the instances, referenced by class, then id string.
195: * </p>
196: */
197: private Map instances = new HashMap();
198: /**
199: * <p>
200: * Stores the mask factory used throughout this application.
201: * </p>
202: * <p>
203: * <b>Note </b> we have overridden the name of the list mask used by default
204: * to just "list" - you could do the same for the default input
205: * mask. (This dictates the name of the <strong>Struts </strong> action
206: * class used).
207: * </p>
208: */
209: private MaskFactory maskFactory = new DefaultMaskFactory(
210: "inputMask", "list",
211: new DefaultFieldValueConvertorFactory());
212:
213: /**
214: * <p>
215: * Private default constructor enforces singleton behavior.
216: * </p>
217: */
218: private Catalog() {
219: reset();
220: initializeDO(CustomerDO.class);
221: initializeDO(OrderDO.class);
222: initializeDO(OrderItemDO.class);
223: initializeDO(ProductDO.class);
224: }
225:
226: /**
227: * <p>
228: * Add a new object to be persisted.
229: * </p>
230: *
231: * @param session open persistence session, used to store the object.
232: * @param valueObject
233: * value object to be persisted.
234: * @return new value object with id set appropriately.
235: * @throws PersistenceException thrown by the underlying persistence
236: * mechanism if the object cannot be saved for any reason.
237: * @see com.ivata.mask.persistence.PersistenceManager#add
238: */
239: public synchronized ValueObject add(
240: final PersistenceSession session,
241: final ValueObject valueObject) throws PersistenceException {
242: if (logger.isDebugEnabled()) {
243: logger.debug("add(PersistenceSession session = " + session
244: + ", ValueObject valueObject = " + valueObject
245: + ") - start");
246: }
247:
248: String className = valueObject.getClass().getName();
249: Integer counter = (Integer) counters.get(className);
250: Map instancesThisClass = (Map) instances.get(className);
251: if (counter == null) {
252: throw new NullPointerException(
253: "ERROR: unknown value object class '"
254: + valueObject.getClass() + "'");
255: }
256: counter = new Integer(counter.intValue() + 1);
257: counters.put(className, counter);
258: DemoValueObject demoValueObject = (DemoValueObject) valueObject;
259: demoValueObject.setId(counter.intValue());
260: instancesThisClass.put(demoValueObject.getIdString(),
261: demoValueObject);
262:
263: if (logger.isDebugEnabled()) {
264: logger.debug("add(PersistenceSession, ValueObject) "
265: + "- end - return value = " + demoValueObject);
266: }
267: return demoValueObject;
268: }
269:
270: /**
271: * <p>
272: * Persist changes to an existing object.
273: * </p>
274: *
275: * @param session open persistence session, used to store the changes to
276: * the object.
277: * @param valueObject value object to be persisted.
278: * @throws PersistenceException thrown by the underlying persistence
279: * mechanism if the object cannot be saved for any reason.
280: * @see com.ivata.mask.persistence.PersistenceManager#amend
281: */
282: public synchronized void amend(final PersistenceSession session,
283: final ValueObject valueObject) throws PersistenceException {
284: if (logger.isDebugEnabled()) {
285: logger.debug("amend(PersistenceSession session = "
286: + session + ", ValueObject valueObject = "
287: + valueObject + ") - start");
288: }
289:
290: DemoValueObject demoValueObject = (DemoValueObject) valueObject;
291: String className = valueObject.getClass().getName();
292: Map instancesThisClass = (Map) instances.get(className);
293: if (instancesThisClass == null) {
294: throw new NullPointerException(
295: "ERROR: unknown value object class '"
296: + valueObject.getClass() + "'");
297: }
298: ValueObject existingObject = (ValueObject) instancesThisClass
299: .get(demoValueObject.getIdString());
300: setPropertyValues(demoValueObject, existingObject);
301:
302: if (logger.isDebugEnabled()) {
303: logger
304: .debug("amend(PersistenceSession, ValueObject) - end");
305: }
306: }
307:
308: /**
309: * Retrieve all instances of a particular class. This method is used in
310: * the list pages.
311: *
312: * @param session open persistence session, used to fetch the objects.
313: * @param classParam class of objects to be retrieved. Must be a subclass
314: * of <code>ValueObject</code>.
315: * @return <code>List</code> containing instances of the requested class.
316: * @see com.ivata.mask.PersistenceManager#locateByBaseClass
317: */
318: public List findAll(final PersistenceSession session,
319: final Class classParam) {
320: if (logger.isDebugEnabled()) {
321: logger.debug("findAll(PersistenceSession session = "
322: + session + ", Class classParam = " + classParam
323: + ") - start");
324: }
325:
326: Map instancesThisClass = getInstanceMap(classParam);
327: List returnList = new Vector(instancesThisClass.values());
328: if (logger.isDebugEnabled()) {
329: logger.debug("findAll(PersistenceSession, Class) - end"
330: + " - return value = " + returnList);
331: }
332: return returnList;
333: }
334:
335: /**
336: * Retrieve a single instance of a particular class. This method is used in
337: * the input/display pages.
338: *
339: * @param session open persistence session, used to fetch the object.
340: * @param classParam class of object to be retrieved. Must be a subclass
341: * of <code>ValueObject</code>.
342: * @param key unique identifier of the object to retrieve.
343: * @return instance of the requested class which matches the identifier
344: * <code>key</code>.
345: * @throws PersistenceException thrown by the underlying persistence
346: * mechanism if the object cannot be retrieved for any reason.
347: * @see com.ivata.mask.persistence.PersistenceManager#findByPrimaryKey
348: */
349: public ValueObject findByPrimaryKey(
350: final PersistenceSession session, final Class classParam,
351: final Serializable key) throws PersistenceException {
352: if (logger.isDebugEnabled()) {
353: logger
354: .debug("findByPrimaryKey(PersistenceSession session = "
355: + session
356: + ", Class classParam = "
357: + classParam
358: + ", Serializable key = "
359: + key + ") - start");
360: }
361:
362: Map instancesThisClass = getInstanceMap(classParam);
363: ValueObject returnValueObject = (ValueObject) instancesThisClass
364: .get(key);
365: if (logger.isDebugEnabled()) {
366: logger.debug("findByPrimaryKey(PersistenceSession, "
367: + "Class, Serializable) - end - return value = "
368: + returnValueObject);
369: }
370: return returnValueObject;
371: }
372:
373: /**
374: * Class instances are stored internally within a map, indexed by the
375: * object's identifier. This private helper retrieves the map for a given
376: * class.
377: *
378: * @param classParam indicates the class of value objects which should be
379: * indexed by the map
380: * @return a map of all value objects matching the requested class.
381: */
382: private Map getInstanceMap(final Class classParam) {
383: if (logger.isDebugEnabled()) {
384: logger.debug("getInstanceMap(Class classParam = "
385: + classParam + ") - start");
386: }
387:
388: Map instancesThisClass = null;
389: Class baseClass = classParam;
390: while (instancesThisClass == null) {
391: if ((baseClass == null)
392: || "java.lang.Object".equals(baseClass.getName())) {
393: throw new RuntimeException(
394: "ERROR in value object locator: no "
395: + "instances for class '"
396: + classParam.getName() + "'");
397: }
398: instancesThisClass = (Map) instances.get(baseClass
399: .getName());
400: baseClass = baseClass.getSuperclass();
401: }
402:
403: if (logger.isDebugEnabled()) {
404: logger
405: .debug("getInstanceMap(Class) - end - return value = "
406: + instancesThisClass);
407: }
408: return instancesThisClass;
409: }
410:
411: /**
412: * <p>
413: * Get the mask factory used throughout this application. This is used to
414: * create and retrieve input/list masks.
415: * </p>
416: *
417: * @return mask factory used throughout this application.
418: */
419: public synchronized MaskFactory getMaskFactory() {
420: if (logger.isDebugEnabled()) {
421: logger.debug("getMaskFactory() - start");
422: }
423:
424: if (logger.isDebugEnabled()) {
425: logger.debug("getMaskFactory() - end - return value = "
426: + maskFactory);
427: }
428: return maskFactory;
429: }
430:
431: /**
432: * <p>
433: * Initialize the map entries for a particular value object. All value
434: * objects are stored in maps - this creates the map for value objects of a
435: * single class.
436: * </p>
437: *
438: * @param valueObjectClass class of value object whose internal storage
439: * map should be prepared.
440: */
441: private void initializeDO(final Class valueObjectClass) {
442: if (logger.isDebugEnabled()) {
443: logger.debug("initializeDO(Class valueObjectClass = "
444: + valueObjectClass + ") - start");
445: }
446:
447: String className = valueObjectClass.getName();
448: counters.put(className, new Integer(0));
449: instances.put(className, new HashMap());
450:
451: if (logger.isDebugEnabled()) {
452: logger.debug("initializeDO(Class) - end");
453: }
454: }
455:
456: /**
457: * <p>
458: * Open a catalog session.
459: * </p>
460: *
461: * @see com.ivata.mask.persistence.PersistenceManager#openSession()
462: */
463: public PersistenceSession openSession() throws PersistenceException {
464: if (logger.isDebugEnabled()) {
465: logger.debug("openSession() - start");
466: }
467:
468: PersistenceSession returnPersistenceSession = new CatalogSession();
469: if (logger.isDebugEnabled()) {
470: logger.debug("openSession() - end - return value = "
471: + returnPersistenceSession);
472: }
473: return returnPersistenceSession;
474: }
475:
476: /**
477: * <p>
478: * Open a catalog session.
479: * </p>
480: *
481: * @see com.ivata.mask.persistence.PersistenceManager#openSession()
482: */
483: public PersistenceSession openSession(
484: final HttpSession webSessionParam)
485: throws PersistenceException {
486: if (logger.isDebugEnabled()) {
487: logger.debug("openSession(HttpSession webSessionParam = "
488: + webSessionParam + ") - start");
489: }
490:
491: PersistenceSession returnPersistenceSession = new CatalogSession();
492: if (logger.isDebugEnabled()) {
493: logger
494: .debug("openSession(HttpSession) - end - return value = "
495: + returnPersistenceSession);
496: }
497: return returnPersistenceSession;
498: }
499:
500: /**
501: * You can't use a system session in this simple demo. This method throws
502: * an exception of class <code>UnsupportedOperationException</code>.
503: *
504: * @param systemSession
505: * @return
506: * @throws PersistenceException
507: * @see com.ivata.mask.persistence.PersistenceManager#openSession
508: */
509: public PersistenceSession openSession(final Object systemSession)
510: throws PersistenceException {
511: if (logger.isDebugEnabled()) {
512: logger.debug("openSession(Object systemSession = "
513: + systemSession + ") - start");
514: }
515:
516: // NOTE: the demo application ignores the system session!
517: PersistenceSession returnPersistenceSession = openSession();
518: if (logger.isDebugEnabled()) {
519: logger.debug("openSession(Object) - end - return value = "
520: + returnPersistenceSession);
521: }
522: return returnPersistenceSession;
523: }
524:
525: /**
526: * <p>
527: * Remove a value object from the system.
528: * </p>
529: *
530: * @param session open persistence session, used to remove the object.
531: * @param classParam class of object to be retrieved. Must be a subclass
532: * of <code>ValueObject</code>.
533: * @param key unique identifier of the object to retrieve.
534: * @throws PersistenceException thrown by the underlying persistence
535: * mechanism if the object cannot be removed for any reason.
536: * @see com.ivata.mask.persistence.PersistenceManager#remove
537: */
538: public void remove(final PersistenceSession session,
539: final Class classParam, final Serializable key)
540: throws PersistenceException {
541: if (logger.isDebugEnabled()) {
542: logger.debug("remove(PersistenceSession session = "
543: + session + ", Class classParam = " + classParam
544: + ", Serializable key = " + key + ") - start");
545: }
546:
547: assert (classParam != null);
548: assert (key != null);
549:
550: Map instancesThisClass = (Map) instances.get(classParam
551: .getName());
552: instancesThisClass.remove(key);
553: // also have to cascade changes to order/order item - your perswistence
554: // layer (such as hibernate) should take care of this stuff, in real
555: // life
556: if ("com.ivata.mask.web.demo.customer.CustomerDO"
557: .equals(classParam.getName())) {
558: Map orderInstances = (Map) instances
559: .get("com.ivata.mask.web.demo.order.OrderDO");
560: Iterator keyIterator = orderInstances.keySet().iterator();
561: while (keyIterator.hasNext()) {
562: String orderId = (String) keyIterator.next();
563: OrderDO order = (OrderDO) orderInstances.get(orderId);
564: if ((order.getCustomer() != null)
565: && key
566: .equals(order.getCustomer()
567: .getIdString())) {
568: order.setCustomer(null);
569: }
570: }
571: } else if ("com.ivata.mask.web.demo.product.ProductDO"
572: .equals(classParam.getName())) {
573: Map orderItemInstances = (Map) instances
574: .get("com.ivata.mask.web.demo.order.item.OrderItemDO");
575: Iterator keyIterator = orderItemInstances.keySet()
576: .iterator();
577: while (keyIterator.hasNext()) {
578: String orderItemId = (String) keyIterator.next();
579: OrderItemDO orderItem = (OrderItemDO) orderItemInstances
580: .get(orderItemId);
581: if ((orderItem.getProduct() != null)
582: && key.equals(orderItem.getProduct()
583: .getIdString())) {
584: orderItem.setProduct(null);
585: }
586: }
587: }
588:
589: if (logger.isDebugEnabled()) {
590: logger
591: .debug("remove(PersistenceSession, Class, Serializable)"
592: + " - end");
593: }
594: }
595:
596: /**
597: * <p>
598: * Reset the catalog to its initial state.
599: * </p>
600: */
601: public void reset() {
602: if (logger.isDebugEnabled()) {
603: logger.debug("reset() - start");
604: }
605:
606: // go thro' all the classes and reset...
607: Set keySet = instances.keySet();
608: for (Iterator keyIterator = keySet.iterator(); keyIterator
609: .hasNext();) {
610: String className = (String) keyIterator.next();
611: Map instancesThisClass = (Map) instances.get(className);
612: instancesThisClass.clear();
613: counters.put(className, new Integer(0));
614: }
615:
616: if (logger.isDebugEnabled()) {
617: logger.debug("reset() - end");
618: }
619: }
620:
621: /**
622: * <p>
623: * Set the mask factory used throughout this application.
624: * </p>
625: *
626: * @param factory
627: * mask factory used throughout this application.
628: */
629: public void setMaskFactory(final MaskFactory factory) {
630: if (logger.isDebugEnabled()) {
631: logger.debug("setMaskFactory(MaskFactory factory = "
632: + factory + ") - start");
633: }
634:
635: maskFactory = factory;
636:
637: if (logger.isDebugEnabled()) {
638: logger.debug("setMaskFactory(MaskFactory) - end");
639: }
640: }
641:
642: /**
643: * <p>
644: * Override to set all property values from one value object to another.
645: * </p>
646: *
647: * @param from
648: * value object to take values from.
649: * @param to
650: * value object to set values in.
651: */
652: protected void setPropertyValues(final ValueObject from,
653: final ValueObject to) {
654: if (logger.isDebugEnabled()) {
655: logger.debug("setPropertyValues(ValueObject from = " + from
656: + ", ValueObject to = " + to + ") - start");
657: }
658:
659: // go thro' and set all properties
660: PropertyDescriptor[] descriptors = PropertyUtils
661: .getPropertyDescriptors(to.getClass());
662: for (int i = 0; i < descriptors.length; i++) {
663: PropertyDescriptor descriptor = descriptors[i];
664: String propertyName = descriptor.getName();
665: try {
666: PropertyUtils.setProperty(to, propertyName,
667: PropertyUtils.getProperty(from, propertyName));
668: } catch (NoSuchMethodException e) {
669: // this is probably ok
670: if (!("idString".equals(propertyName) || "class"
671: .equals(propertyName))) {
672: logger.warn("Warning (" + e.getClass().getName()
673: + "): no setter method for property '"
674: + propertyName + "' on value object '" + to
675: + "'");
676: }
677: } catch (Exception e) {
678: logger.error(
679: "setPropertyValues(ValueObject, ValueObject)",
680: e);
681:
682: try {
683: throw new ValueObjectException(e, to.getClass(),
684: "setting property called '" + propertyName
685: + "'");
686: } catch (ValueObjectException e1) {
687: logger
688: .error(
689: "setPropertyValues(ValueObject, ValueObject)",
690: e1);
691:
692: // TODO Auto-generated catch block
693: e1.printStackTrace();
694: }
695: }
696: }
697:
698: if (logger.isDebugEnabled()) {
699: logger
700: .debug("setPropertyValues(ValueObject, ValueObject) - end");
701: }
702: }
703: }
|