001: package velosurf.context;
002:
003: import velosurf.model.Entity;
004: import velosurf.util.Logger;
005:
006: import java.util.Map;
007: import java.util.HashMap;
008: import java.lang.reflect.Method;
009:
010: /**
011: * <p>This wrapper allows one to specify custom mapping objects that don't inherit from Instance.</p>
012: * <p>For now, the introspection is rather basic but may work for standard getters without ambiguity.</p>
013: *
014: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
015: */
016: public class ExternalObjectWrapper extends Instance {
017:
018: /** Builds a new PlaiObjectWrapper.
019: *
020: * @param entity the related entity
021: * @param object target object
022: */
023: public ExternalObjectWrapper(Entity entity, Object object) {
024: super (entity);
025: wrapped = object;
026: Class clazz = wrapped.getClass();
027: classInfo = classInfoMap.get(clazz.getName());
028: if (classInfo == null) {
029: classInfo = new ClassInfo(clazz);
030: classInfoMap.put(clazz.getName(), classInfo);
031: }
032: }
033:
034: /**
035: * Wrapper generic getter. Tries first to get the property from the wrapped object, and falls back to the superclass
036: * if not found.
037: *
038: * @param key key of the property to be returned
039: * @return a String, an Instance, an AttributeReference or null if not found or if an error
040: * occurs
041: */
042: public Object get(Object key) {
043: Object ret = getExternal(key);
044: if (ret == null)
045: ret = super .get(key);
046: return ret;
047: }
048:
049: /**
050: * External getter. Get a value on the external object
051: *
052: * @param key key of the property to be returned
053: * @return a String, an Instance, an AttributeReference or null if not found or if an error
054: * occurs
055: */
056: public Object getExternal(Object key) {
057: Method m = classInfo.getGetter((String) key);
058: if (m != null) {
059: try {
060: return m
061: .invoke(
062: wrapped,
063: m.getParameterTypes().length > 0 ? new Object[] { key }
064: : new Object[] {}); // return even if result is null
065: } catch (Exception e) {
066: Logger.warn("could not get value of field "
067: + getEntity().getName() + "." + key
068: + "... falling back to the Instance getter");
069: Logger.log(e);
070: }
071: }
072: return null;
073: }
074:
075: /**
076: * Wrapper generic setter. Tries first to set the property into the wrapped object, and falls back to the superclass
077: * if not found.
078: * @param key key of the property to be set
079: * @param value corresponding value
080: * @return previous value, or null
081: */
082: public Object put(String key, Object value) {
083: Method m = classInfo.getSetter((String) key);
084: if (m != null) {
085: try {
086: return m
087: .invoke(
088: wrapped,
089: m.getParameterTypes().length == 2 ? new Object[] {
090: key, value }
091: : new Object[] { value });
092: } catch (Exception e) {
093: Logger.warn("could not set value of field "
094: + getEntity().getName() + "." + key + " to '"
095: + value
096: + "'... falling back to the Instance setter");
097: Logger.log(e);
098: }
099: }
100: return super .put(key, value);
101: }
102:
103: /** <p>Try to update the row associated with this Instance using an update() method
104: * in the external object.</p>
105: *
106: * @return <code>true</code> if successfull, <code>false</code> if an error
107: * occurs (in which case $db.error can be checked).
108: */
109: public boolean update() {
110: Method m = classInfo.getUpdate1();
111: if (m != null) {
112: Class cls = m.getReturnType();
113: Object args[] = {};
114: try {
115: if (cls == boolean.class || cls == Boolean.class) {
116: return ((Boolean) m.invoke(wrapped, args))
117: .booleanValue();
118: } else {
119: Logger
120: .warn("external object wrapper: update method should return boolean or Boolean. Actual result will be ignored.");
121: m.invoke(wrapped, args);
122: return true;
123: }
124: } catch (Exception e) {
125: Logger.warn("method" + cls.getName()
126: + ".update() throw exception: " + e);
127: return false;
128: }
129: } else
130: return super .update();
131: }
132:
133: /** <p>Try to update the row associated with this Instance using an update(map) method
134: * in the external object.</p>
135: *
136: * @return <code>true</code> if successfull, <code>false</code> if an error
137: * occurs (in which case $db.error can be checked).
138: */
139: public boolean update(Map values) {
140: Method m = classInfo.getUpdate2();
141: if (m != null) {
142: Class cls = m.getReturnType();
143: Object args[] = { values };
144: try {
145: if (cls == boolean.class || cls == Boolean.class) {
146: return ((Boolean) m.invoke(wrapped, args))
147: .booleanValue();
148: } else {
149: Logger
150: .warn("external object wrapper: update method should return boolean or Boolean. Actual result will be ignored.");
151: m.invoke(wrapped, args);
152: return true;
153: }
154: } catch (Exception e) {
155: Logger.warn("method" + cls.getName()
156: + ".update() throw exception: " + e);
157: return false;
158: }
159: } else
160: return super .update();
161: }
162:
163: /** <p>Tries to delete the row associated with this Instance using a delete() method in the external object.
164: * Velosurf will ensure all key columns are specified, to avoid an accidental massive update.</p>
165: *
166: * @return <code>true</code> if successfull, <code>false</code> if an error
167: * occurs (in which case $db.error can be checked).
168: */
169: public boolean delete() {
170: Method m = classInfo.getDelete();
171: if (m != null) {
172: Class cls = m.getReturnType();
173: Object args[] = {};
174: try {
175: if (cls == boolean.class || cls == Boolean.class) {
176: return ((Boolean) m.invoke(wrapped, args))
177: .booleanValue();
178: } else {
179: Logger
180: .warn("external object wrapper: delete method should return boolean or Boolean. Actual result will be ignored.");
181: m.invoke(wrapped, args);
182: return true;
183: }
184: } catch (Exception e) {
185: Logger.warn("method" + cls.getName()
186: + ".delete() throw exception: " + e);
187: return false;
188: }
189: } else
190: return super .delete();
191: }
192:
193: /** Tries to insert a new row corresponding to this Instance using an insert() method in the external object.
194: *
195: * @return <code>true</code> if successfull, <code>false</code> if an error
196: * occurs (in which case $db.error can be checked).
197: */
198: public boolean insert() {
199: Method m = classInfo.getInsert();
200: if (m != null) {
201: Class cls = m.getReturnType();
202: Object args[] = {};
203: try {
204: if (cls == boolean.class || cls == Boolean.class) {
205: return ((Boolean) m.invoke(wrapped, args))
206: .booleanValue();
207: } else {
208: Logger
209: .warn("external object wrapper: insert method should return boolean or Boolean. Actual result will be ignored.");
210: m.invoke(wrapped, args);
211: return true;
212: }
213: } catch (Exception e) {
214: Logger.warn("method" + cls.getName()
215: + ".delete() throw exception: " + e);
216: return false;
217: }
218: } else
219: return super .insert();
220: }
221:
222: /**
223: * Returns the underlying external object.
224: *
225: * @return the external object
226: */
227: public Object unwrap() {
228: return wrapped;
229: }
230:
231: /** The wrapped object. */
232: Object wrapped = null;
233:
234: /** Info on the wrapped object class. */
235: ClassInfo classInfo = null;
236:
237: /** A map of class infos. */
238: static Map<String, ClassInfo> classInfoMap = new HashMap<String, ClassInfo>();
239:
240: /** A cache for the wrapped object getter methods. */
241: Map getterCache = null;
242:
243: /** A cache for the wrapped object setter methods. */
244: Map setterCache = null;
245:
246: /** This private class gathers informations on the class of wrapped objects. */
247: static private class ClassInfo {
248: ClassInfo(Class clazz) {
249: this .clazz = clazz;
250: }
251:
252: /**
253: * Getter getter :-) .
254: * @param key property name
255: * @return property getter, if found
256: */
257: Method getGetter(String key) {
258: Method result = (Method) getterMap.get(key);
259: if (result == noSuchMethod) {
260: return null;
261: }
262: if (result != null) {
263: return result;
264: }
265:
266: Class[] types = {};
267:
268: // getFoo
269: StringBuffer sb = new StringBuffer("get");
270: sb.append(key);
271: try {
272: result = clazz.getMethod(sb.toString(), types);
273: getterMap.put(key, result);
274: return result;
275: } catch (NoSuchMethodException nsme) {
276: }
277:
278: // getfoo
279: char c = sb.charAt(3);
280: sb.setCharAt(3, Character.isLowerCase(c) ? Character
281: .toUpperCase(c) : Character.toLowerCase(c));
282: try {
283: result = clazz.getMethod(sb.toString(), types);
284: getterMap.put(key, result);
285: return result;
286: } catch (NoSuchMethodException nsme) {
287: }
288:
289: // get(foo)
290: result = getGenericGetter();
291: if (result == null) {
292: getterMap.put(key, noSuchMethod);
293: } else {
294: getterMap.put(key, result);
295: }
296: return result;
297:
298: }
299:
300: /**
301: * Setter getter
302: * @param key property name
303: * @return property setter, if found
304: */
305: Method getSetter(String key) {
306: Method result = setterMap.get(key);
307: if (result == noSuchMethod) {
308: return null;
309: }
310: if (result != null) {
311: return result;
312: }
313:
314: Class[] types = {};
315:
316: /* setFoo */
317: StringBuffer sb = new StringBuffer("set");
318: sb.append(key);
319: try {
320: result = clazz.getMethod(sb.toString(), types);
321: setterMap.put(key, result);
322: return result;
323: } catch (NoSuchMethodException nsme) {
324: }
325:
326: /* setfoo */
327: char c = sb.charAt(3);
328: sb.setCharAt(3, Character.isLowerCase(c) ? Character
329: .toUpperCase(c) : Character.toLowerCase(c));
330: try {
331: result = clazz.getMethod(sb.toString(), types);
332: setterMap.put(key, result);
333: return result;
334: } catch (NoSuchMethodException nsme) {
335: }
336:
337: /* put(foo,bar) */
338: result = getGenericSetter();
339: if (result == null) {
340: setterMap.put(key, noSuchMethod);
341: } else {
342: setterMap.put(key, result);
343: }
344: return result;
345: }
346:
347: /**
348: * Tries to get an update() method in the wrapped object.
349: * @return found update method, if any
350: */
351: Method getUpdate1() {
352: if (update1 == noSuchMethod) {
353: return null;
354: }
355: if (update1 != null) {
356: return update1;
357: }
358: try {
359: return update1 = clazz.getMethod("update",
360: new Class[] {});
361: } catch (NoSuchMethodException nsme) {
362: update1 = noSuchMethod;
363: return null;
364: }
365: }
366:
367: /**
368: * Tries to get an update(Map) method in the wrapped object.
369: * @return found update method, if any
370: */
371: Method getUpdate2() {
372: if (update2 == noSuchMethod) {
373: return null;
374: }
375: if (update2 != null) {
376: return update2;
377: }
378: try {
379: return update2 = clazz.getMethod("update",
380: new Class[] { Map.class });
381: } catch (NoSuchMethodException nsme) {
382: update2 = noSuchMethod;
383: return null;
384: }
385: }
386:
387: /**
388: * Tries to get an insert() method in the wrapped object.
389: * @return found method, if any
390: */
391: Method getInsert() {
392: if (insert == noSuchMethod) {
393: return null;
394: }
395: if (insert != null) {
396: return insert;
397: }
398: try {
399: return insert = clazz.getMethod("update",
400: new Class[] { Map.class });
401: } catch (NoSuchMethodException nsme) {
402: insert = noSuchMethod;
403: return null;
404: }
405: }
406:
407: /**
408: * Tries to get a delete() method in the wrapped object.
409: * @return found method, if any
410: */
411: Method getDelete() {
412: if (delete == noSuchMethod) {
413: return null;
414: }
415: if (delete != null) {
416: return delete;
417: }
418: try {
419: return delete = clazz.getMethod("update",
420: new Class[] { Map.class });
421: } catch (NoSuchMethodException nsme) {
422: delete = noSuchMethod;
423: return null;
424: }
425: }
426:
427: /**
428: * Tries to get a generic getter in the wrapped object.
429: * @return found method, if any
430: */
431: Method getGenericGetter() {
432: if (genericGetter == noSuchMethod) {
433: return null;
434: }
435: if (genericGetter != null) {
436: return genericGetter;
437: }
438: Class[] types = new Class[] { Object.class };
439: try {
440: return genericGetter = clazz.getMethod("get", types);
441: } catch (NoSuchMethodException nsme) {
442: genericGetter = noSuchMethod;
443: return null;
444: }
445: }
446:
447: /**
448: * Tries to get a generic setter in the wrapped object.
449: * @return found method, if any
450: */
451: Method getGenericSetter() {
452: if (genericSetter == noSuchMethod) {
453: return null;
454: }
455: if (genericSetter != null) {
456: return genericSetter;
457: }
458: Class[] types = new Class[] { Object.class, Object.class };
459: try {
460: return genericSetter = clazz.getMethod("put", types);
461: } catch (NoSuchMethodException nsme) {
462: genericSetter = noSuchMethod;
463: return null;
464: }
465: }
466:
467: /**
468: * Wrapped class.
469: */
470: Class clazz;
471: /**
472: * Getter map.
473: */
474: Map<String, Method> getterMap = new HashMap<String, Method>();
475: /**
476: * Setter map.
477: */
478: Map<String, Method> setterMap = new HashMap<String, Method>();
479: /**
480: * Generic getter.
481: */
482: Method genericGetter = null;
483: /**
484: * Generic setter.
485: */
486: Method genericSetter = null;
487: /**
488: * Update method, first form.
489: */
490: Method update1 = null;
491: /**
492: * Update method, second form.
493: */
494: Method update2 = null;
495: /**
496: * Insert method.
497: */
498: Method insert = null;
499: /**
500: * Delete method.
501: */
502: Method delete = null;
503: /* dummy method object used to remember we already tried to find an unexistant method. */
504: static Method noSuchMethod;
505:
506: static {
507: try {
508: noSuchMethod = Object.class.getMethod("toString",
509: new Class[] {});
510: } catch (NoSuchMethodException nsme) {
511: }
512: }
513: }
514: }
|