001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.beans.moulding.mapping;
051:
052: import java.beans.BeanInfo;
053: import java.beans.Introspector;
054: import java.beans.PropertyDescriptor;
055: import java.io.File;
056: import java.io.IOException;
057: import java.io.InputStream;
058: import java.util.ArrayList;
059: import java.util.Arrays;
060: import java.util.HashMap;
061: import java.util.Iterator;
062: import java.util.LinkedHashMap;
063: import java.util.List;
064: import java.util.Map;
065: import java.util.Set;
066: import javax.xml.parsers.DocumentBuilder;
067: import javax.xml.parsers.DocumentBuilderFactory;
068: import javax.xml.parsers.ParserConfigurationException;
069: import org.apache.log4j.Logger;
070: import org.jaffa.util.DefaultEntityResolver;
071: import org.jaffa.util.DefaultErrorHandler;
072: import org.jaffa.util.StringHelper;
073: import org.jaffa.util.URLHelper;
074: import org.w3c.dom.Document;
075: import org.w3c.dom.Element;
076: import org.w3c.dom.Node;
077: import org.w3c.dom.NodeList;
078: import org.xml.sax.SAXException;
079:
080: /**
081: * @version 2.0
082: * @author PaulE
083: */
084: public class GraphMapping {
085:
086: private static Logger log = Logger.getLogger(GraphMapping.class);
087:
088: private Class domainClass;
089: private Class dataClass;
090: private Map fieldMap;
091: private Map keyMap;
092: private Map foreignMap;
093: private Map relatedMap;
094: //private List keyFields;
095: private PropertyDescriptor[] dataDescriptors;
096: private Map dataDescriptorMap;
097: private PropertyDescriptor[] domainDescriptors;
098: private Map domainDescriptorMap;
099:
100: public static PropertyDescriptor[] getPropertyDescriptors(
101: Class clazz) {
102: try {
103: return Introspector.getBeanInfo(clazz)
104: .getPropertyDescriptors();
105: } catch (java.beans.IntrospectionException e) {
106: log.error("Can't Introspect Mold Methods", e);
107: return new PropertyDescriptor[] {};
108: }
109: }
110:
111: public static Map getPropertyDescriptorMap(
112: PropertyDescriptor[] props) {
113: Map m = new HashMap();
114: if (props != null && props.length != 0)
115: for (int i = 0; i < props.length; i++) {
116: PropertyDescriptor prop = props[i];
117: m.put(prop.getName(), prop);
118: }
119: return m;
120: }
121:
122: private static final String NODE_DAO_CLASS = "data-access-object";
123: private static final String NODE_DO_CLASS = "domain-object";
124: private static final String NODE_MAP_KEYS = "map-key-fields";
125: private static final String NODE_MAP_FIELDS = "map-fields";
126: private static final String NODE_MAP_FOREIGN = "map-foreign-objects";
127: private static final String NODE_MAP_RELATED = "map-aggregate-objects";
128: private static final String NODE_FIELD = "field";
129: private static final String NODE_FOREIGN_OBJECT = "foreign-object";
130: private static final String NODE_FOREIGN_KEY = "foreign-key-field";
131: private static final String ATTR_CLASS = "class";
132: private static final String ATTR_DAO_FIELD = "dao";
133: private static final String ATTR_DO_FIELD = "do";
134:
135: /** Creates a new instance of Graph Mapper from an XMl definition */
136: GraphMapping(Class dataClass) {
137:
138: String filename = StringHelper.replace(dataClass.getName(),
139: ".", "/")
140: + ".mapping";
141: InputStream stream = null;
142: try {
143: // Create a factory object for creating DOM parsers
144: DocumentBuilderFactory factory = DocumentBuilderFactory
145: .newInstance();
146:
147: // Specifies that the parser produced by this factory will validate documents as they are parsed.
148: factory.setValidating(false);
149:
150: // Now use the factory to create a DOM parser
151: DocumentBuilder parser = factory.newDocumentBuilder();
152:
153: // Specifies the EntityResolver to resolve DTD used in XML documents
154: parser.setEntityResolver(new DefaultEntityResolver());
155:
156: // Specifies the ErrorHandler to handle warning/error/fatalError conditions
157: parser.setErrorHandler(new DefaultErrorHandler());
158:
159: // Parse the file and build a Document tree to represent its content
160: stream = URLHelper.getInputStream(filename);
161: if (stream == null)
162: throw new IOException("File not found: " + filename);
163: Document document = parser.parse(stream);
164:
165: // Get the DAO class, and make sure the loaded one matches the supplied one!
166: NodeList nl = document.getElementsByTagName(NODE_DAO_CLASS);
167: if (nl.getLength() != 1)
168: throw new RuntimeException("Can't find node '"
169: + NODE_DAO_CLASS + "' in file " + filename);
170: Node n = nl.item(0).getAttributes()
171: .getNamedItem(ATTR_CLASS);
172: if (n == null)
173: throw new RuntimeException("Node '" + NODE_DAO_CLASS
174: + "' has no attribute '" + ATTR_CLASS
175: + "' in file " + filename);
176:
177: String clazz = n.getNodeValue();
178: if (!dataClass.getName().equals(clazz))
179: throw new RuntimeException("Expected '"
180: + NODE_DAO_CLASS + "\\" + ATTR_CLASS
181: + "' to have value '" + dataClass.getName()
182: + "' not '" + clazz + "' in file " + filename);
183:
184: this .dataClass = dataClass;
185:
186: // Get the DO class, and make sure the loaded one matches the supplied one!
187: nl = document.getElementsByTagName(NODE_DO_CLASS);
188: if (nl.getLength() != 1)
189: throw new RuntimeException("Can't find node '"
190: + NODE_DO_CLASS + "' in file " + filename);
191: n = nl.item(0).getAttributes().getNamedItem(ATTR_CLASS);
192: if (n == null)
193: throw new RuntimeException("Node '" + NODE_DO_CLASS
194: + "' has no attribute '" + ATTR_CLASS
195: + "' in file " + filename);
196:
197: clazz = n.getNodeValue();
198: try {
199: domainClass = Class.forName(clazz);
200: } catch (ClassNotFoundException e) {
201: throw new RuntimeException("Domain class '" + clazz
202: + "' not found in file " + filename);
203: }
204:
205: // Set up descriptor details
206: dataDescriptors = getPropertyDescriptors(dataClass);
207: domainDescriptors = getPropertyDescriptors(domainClass);
208: dataDescriptorMap = getPropertyDescriptorMap(dataDescriptors);
209: domainDescriptorMap = getPropertyDescriptorMap(domainDescriptors);
210:
211: // Initalize Maps
212: keyMap = new LinkedHashMap();
213: fieldMap = new HashMap();
214: foreignMap = new HashMap();
215: relatedMap = new HashMap();
216:
217: // Get The Key Fields
218: nl = document.getElementsByTagName(NODE_MAP_KEYS);
219: if (nl.getLength() == 1) {
220: Element el = (Element) nl.item(0);
221: NodeList nl2 = el.getElementsByTagName(NODE_FIELD);
222: for (int i = 0; i < nl2.getLength(); i++) {
223: Element el2 = (Element) nl2.item(i);
224: // get do and dao attributes
225: Node nDao = el2.getAttributes().getNamedItem(
226: ATTR_DAO_FIELD);
227: String daoField = null;
228: if (nDao != null)
229: daoField = nDao.getNodeValue();
230: if (daoField == null)
231: throw new RuntimeException("Key field "
232: + (i + 1) + " has missing attribute '"
233: + ATTR_DAO_FIELD + "' in file "
234: + filename);
235:
236: Node nDo = el2.getAttributes().getNamedItem(
237: ATTR_DO_FIELD);
238: String doField = null;
239: if (nDo != null)
240: doField = nDo.getNodeValue();
241: if (doField == null)
242: doField = daoField;
243: keyMap.put(daoField, doField);
244: }
245: }
246:
247: // Get The Normal Fields
248: nl = document.getElementsByTagName(NODE_MAP_FIELDS);
249: if (nl.getLength() == 1) {
250: Element el = (Element) nl.item(0);
251: NodeList nl2 = el.getElementsByTagName(NODE_FIELD);
252: for (int i = 0; i < nl2.getLength(); i++) {
253: Element el2 = (Element) nl2.item(i);
254: // get do and dao attributes
255: Node nDao = el2.getAttributes().getNamedItem(
256: ATTR_DAO_FIELD);
257: String daoField = null;
258: if (nDao != null)
259: daoField = nDao.getNodeValue();
260: if (daoField == null)
261: throw new RuntimeException("Field " + (i + 1)
262: + " has missing attribute '"
263: + ATTR_DAO_FIELD + "' in file "
264: + filename);
265:
266: Node nDo = el2.getAttributes().getNamedItem(
267: ATTR_DO_FIELD);
268: String doField = null;
269: if (nDo != null)
270: doField = nDo.getNodeValue();
271: if (doField == null)
272: doField = daoField;
273: fieldMap.put(daoField, doField);
274: }
275: }
276:
277: // Get The Related Fields
278: nl = document.getElementsByTagName(NODE_MAP_RELATED);
279: if (nl.getLength() == 1) {
280: Element el = (Element) nl.item(0);
281: NodeList nl2 = el.getElementsByTagName(NODE_FIELD);
282: for (int i = 0; i < nl2.getLength(); i++) {
283: Element el2 = (Element) nl2.item(i);
284: // get do and dao attributes
285: Node nDao = el2.getAttributes().getNamedItem(
286: ATTR_DAO_FIELD);
287: String daoField = null;
288: if (nDao != null)
289: daoField = nDao.getNodeValue();
290: if (daoField == null)
291: throw new RuntimeException("Related Object "
292: + (i + 1) + " has missing attribute '"
293: + ATTR_DAO_FIELD + "' in file "
294: + filename);
295:
296: Node nDo = el2.getAttributes().getNamedItem(
297: ATTR_DO_FIELD);
298: String doField = null;
299: if (nDo != null)
300: doField = nDo.getNodeValue();
301: if (doField == null)
302: doField = daoField;
303: relatedMap.put(daoField, doField);
304: }
305: }
306:
307: // Get The Foreign Objects
308: nl = document.getElementsByTagName(NODE_MAP_FOREIGN);
309: if (nl.getLength() == 1) {
310: Element el = (Element) nl.item(0);
311: NodeList nl2 = el
312: .getElementsByTagName(NODE_FOREIGN_OBJECT);
313: for (int i = 0; i < nl2.getLength(); i++) {
314: Element el2 = (Element) nl2.item(i);
315: // get do and dao attributes
316: Node nDao = el2.getAttributes().getNamedItem(
317: ATTR_DAO_FIELD);
318: String daoField = null;
319: if (nDao != null)
320: daoField = nDao.getNodeValue();
321: if (daoField == null)
322: throw new RuntimeException("Foreign Object "
323: + (i + 1) + " has missing attribute '"
324: + ATTR_DAO_FIELD + "' in file "
325: + filename);
326:
327: Node nDo = el2.getAttributes().getNamedItem(
328: ATTR_DO_FIELD);
329: String doField = null;
330: if (nDo != null)
331: doField = nDo.getNodeValue();
332: if (doField == null)
333: doField = daoField;
334:
335: ArrayList foreign = new ArrayList();
336: foreign.add(doField);
337:
338: // Now read the foreign key fields
339: NodeList nl3 = el2
340: .getElementsByTagName(NODE_FOREIGN_KEY);
341: if (nl3.getLength() == 0)
342: throw new RuntimeException("Foreign object '"
343: + daoField + "' has nodes '"
344: + NODE_FOREIGN_KEY + "' in file "
345: + filename);
346: for (int i2 = 0; i2 < nl3.getLength(); i2++) {
347: Element el3 = (Element) nl3.item(i2);
348:
349: Node nf = el3.getAttributes().getNamedItem(
350: ATTR_DO_FIELD);
351: String dof = null;
352: if (nf != null)
353: dof = nf.getNodeValue();
354: if (dof == null)
355: throw new RuntimeException(
356: "Foreign object '"
357: + daoField
358: + "\\"
359: + (i + 1)
360: + " has missing attribute '"
361: + ATTR_DO_FIELD
362: + "' in file " + filename);
363:
364: foreign.add(dof);
365: }
366:
367: foreignMap.put(daoField, foreign
368: .toArray(new String[] {}));
369: }
370: }
371: } catch (ParserConfigurationException e) {
372: log.error("Can't Parse File " + filename, e);
373: throw new InstantiationError("Can't Parse File " + filename);
374: } catch (SAXException e) {
375: log.error("Can't Parse File " + filename, e);
376: throw new InstantiationError("Can't Parse File " + filename);
377: } catch (IOException e) {
378: log.error("Can't Parse File " + filename, e);
379: throw new InstantiationError("Can't Parse File " + filename);
380: } finally {
381: if (stream != null)
382: try {
383: stream.close();
384: } catch (IOException e) {
385: // We don't care about this error!
386: }
387: }
388:
389: // Validate mappings
390: validateMapping();
391: }
392:
393: /** Creates a new instance of DomainObjectGraph */
394: protected GraphMapping(Class dataClass, Class domainClass,
395: Map keyMap, Map fieldMap, Map foreignMap, Map relatedMap,
396: PropertyDescriptor[] dataDescriptors,
397: Map dataDescriptorMap,
398: PropertyDescriptor[] domainDescriptors,
399: Map domainDescriptorMap) {
400: this .dataClass = dataClass;
401: this .domainClass = domainClass;
402: this .dataDescriptors = dataDescriptors;
403: this .dataDescriptorMap = dataDescriptorMap;
404: this .domainDescriptors = domainDescriptors;
405: this .domainDescriptorMap = domainDescriptorMap;
406: this .keyMap = (keyMap == null ? new HashMap() : keyMap);
407: this .fieldMap = (fieldMap == null ? new HashMap() : fieldMap);
408: this .foreignMap = (foreignMap == null ? new HashMap()
409: : foreignMap);
410: this .relatedMap = (relatedMap == null ? new HashMap()
411: : relatedMap);
412:
413: // Validate mappings
414: validateMapping();
415: }
416:
417: private void validateMapping() throws InstantiationError {
418: // Check all mapped fields have valid descriptors
419: String[] names = getDataFieldNames();
420: for (int i = 0; i < names.length; i++) {
421: String name = names[i];
422: PropertyDescriptor d = getDataFieldDescriptor(name);
423: if (d == null)
424: throw new InstantiationError("Missing DAO Property : "
425: + name + " on " + getDataClassShortName());
426: if (d.getReadMethod() == null)
427: throw new InstantiationError(
428: "Missing DAO getter for : " + name + " on "
429: + getDataClassShortName());
430: if (d.getWriteMethod() == null)
431: throw new InstantiationError(
432: "Missing DAO setter for : " + name + " on "
433: + getDataClassShortName());
434: d = getDomainFieldDescriptor(name);
435: if (d == null)
436: throw new InstantiationError("Missing DO Property : "
437: + getDomainFieldName(name) + " on "
438: + getDomainClassShortName());
439: if (d.getReadMethod() == null)
440: throw new InstantiationError("Missing DO getter for : "
441: + getDomainFieldName(name) + " on "
442: + getDomainClassShortName());
443: //if(d.getWriteMethod() == null)
444: // throw new InstantiationError("Missing DO setter for : " + getDomainFieldName(name) + " on " + getDomainClassShortName());
445: }
446: // Check all foreign key fields
447: for (Iterator it = getForeignFields().iterator(); it.hasNext();) {
448: String field = (String) it.next();
449: List foreignKeys = getForeignKeys(field);
450: for (Iterator k = foreignKeys.iterator(); k.hasNext();) {
451: String name = (String) k.next();
452: PropertyDescriptor d = getRealDomainFieldDescriptor(name);
453:
454: if (d == null)
455: throw new InstantiationError(
456: "Missing DO Property : " + name + " on "
457: + getDomainClassShortName()
458: + " for foreign object " + field);
459: if (d.getReadMethod() == null)
460: throw new InstantiationError(
461: "Missing DO getter for : " + name + " on "
462: + getDomainClassShortName()
463: + " for foreign object " + field);
464: if (d.getWriteMethod() == null)
465: throw new InstantiationError(
466: "Missing DO setter for : " + name + " on "
467: + getDomainClassShortName()
468: + " for foreign object " + field);
469: }
470: }
471:
472: }
473:
474: public Class getDataClass() {
475: return dataClass;
476: }
477:
478: public String getDataClassName() {
479: return dataClass.getName();
480: }
481:
482: public String getDataClassShortName() {
483: return shortName(getDataClassName());
484: }
485:
486: public Class getDomainClass() {
487: return domainClass;
488: }
489:
490: public String getDomainClassName() {
491: return domainClass.getName();
492: }
493:
494: public String getDomainClassShortName() {
495: return shortName(getDomainClassName());
496: }
497:
498: public String[] getDataFieldNames() {
499: ArrayList a = new ArrayList();
500: a.addAll(keyMap.keySet());
501: a.addAll(fieldMap.keySet());
502: a.addAll(foreignMap.keySet());
503: a.addAll(relatedMap.keySet());
504: return (String[]) a.toArray(new String[] {});
505: }
506:
507: public boolean containsDataField(String dataFieldName) {
508: return isKeyField(dataFieldName) || isField(dataFieldName)
509: || isForeignField(dataFieldName)
510: || isRelatedField(dataFieldName);
511: }
512:
513: public boolean isKeyField(String dataFieldName) {
514: return keyMap.containsKey(dataFieldName);
515: }
516:
517: public boolean hasKeyFields() {
518: return (keyMap != null && keyMap.size() > 0);
519: }
520:
521: public Set getKeyFields() {
522: return keyMap.keySet();
523: }
524:
525: public boolean isField(String dataFieldName) {
526: return fieldMap.containsKey(dataFieldName);
527: }
528:
529: public boolean hasFields() {
530: return (fieldMap != null && fieldMap.size() > 0);
531: }
532:
533: public Set getFields() {
534: return fieldMap.keySet();
535: }
536:
537: public boolean isForeignField(String dataFieldName) {
538: return foreignMap.containsKey(dataFieldName);
539: }
540:
541: public List getForeignKeys(String dataFieldName) {
542: List l = new ArrayList();
543: Object o = foreignMap.get(dataFieldName);
544: if (o instanceof String[]) {
545: String[] x = (String[]) o;
546: for (int i = 1; i < x.length; i++)
547: l.add(x[i]);
548: }
549: return l;
550: }
551:
552: public Set getForeignFields() {
553: return foreignMap.keySet();
554: }
555:
556: public boolean hasForeignFields() {
557: return (foreignMap != null && foreignMap.size() > 0);
558: }
559:
560: public boolean isRelatedField(String dataFieldName) {
561: return relatedMap.containsKey(dataFieldName);
562: }
563:
564: public boolean hasRelatedFields() {
565: return (relatedMap != null && relatedMap.size() > 0);
566: }
567:
568: public Set getRelatedFields() {
569: return relatedMap.keySet();
570: }
571:
572: public String getDomainFieldName(String dataFieldName) {
573: String name = null;
574: if (keyMap.containsKey(dataFieldName))
575: name = (String) keyMap.get(dataFieldName);
576: else if (fieldMap.containsKey(dataFieldName))
577: name = (String) fieldMap.get(dataFieldName);
578: else if (foreignMap.containsKey(dataFieldName)) {
579: Object o = foreignMap.get(dataFieldName);
580: if (o != null) {
581: if (o instanceof String)
582: name = (String) o;
583: else if (o instanceof String[])
584: name = ((String[]) o)[0];
585: }
586: } else if (relatedMap.containsKey(dataFieldName))
587: name = (String) relatedMap.get(dataFieldName);
588: else
589: return null;
590: return (name == null ? dataFieldName : name);
591: }
592:
593: public PropertyDescriptor getDataFieldDescriptor(
594: String dataFieldName) {
595: PropertyDescriptor pd = (PropertyDescriptor) dataDescriptorMap
596: .get(dataFieldName);
597: if (pd == null)
598: log.error("Tried to access descriptor for invalid field "
599: + getDataClassShortName() + "." + dataFieldName);
600: return pd;
601: }
602:
603: public PropertyDescriptor getDomainFieldDescriptor(
604: String dataFieldName) {
605: if (containsDataField(dataFieldName)) {
606: String domain = getDomainFieldName(dataFieldName);
607: if (domain != null)
608: return (PropertyDescriptor) domainDescriptorMap
609: .get(domain);
610: }
611: return null;
612: }
613:
614: public PropertyDescriptor getRealDomainFieldDescriptor(
615: String domainFieldName) {
616: if (domainFieldName != null)
617: return (PropertyDescriptor) domainDescriptorMap
618: .get(domainFieldName);
619: return null;
620: }
621:
622: private String shortName(String s) {
623: int p = s.lastIndexOf(".");
624: if (p <= 0)
625: return s;
626: else
627: return s.substring(p + 1);
628: }
629: }
|