001 package org.apache.ojb.broker.core.proxy;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.lang.reflect.Constructor;
019 import java.lang.reflect.InvocationTargetException;
020 import java.lang.reflect.Modifier;
021 import java.util.Collection;
022 import java.util.List;
023 import java.util.Set;
024
025 import org.apache.ojb.broker.Identity;
026 import org.apache.ojb.broker.ManageableCollection;
027 import org.apache.ojb.broker.PBKey;
028 import org.apache.ojb.broker.PersistenceBrokerException;
029 import org.apache.ojb.broker.metadata.MetadataException;
030 import org.apache.ojb.broker.query.Query;
031 import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
032 import org.apache.ojb.broker.util.logging.Logger;
033 import org.apache.ojb.broker.util.logging.LoggerFactory;
034
035 /**
036 * Abstract implementation for the ProxyFactory
037 *
038 * @author andrew.clute
039 * @version $Id:
040 */
041 public abstract class AbstractProxyFactory implements ProxyFactory
042 {
043 /*
044 arminw:
045 Do we need a serializable ProxyFactory implementation? If yes, we have to fix this class.
046 TODO: ProxyFactory is declared as Serializable but Constructor class is not, but used without transient keyword.
047 */
048
049 private static Logger log = LoggerFactory.getLogger(AbstractProxyFactory.class);
050 private static transient ProxyFactory singleton;
051
052 /** The indirection handler class */
053 private Class _indirectionHandlerClass;
054 /** The constructor used for creating indirection handler instances (shortcut) */
055 private transient Constructor _indirectionHandlerConstructor;
056 /** The constructor used for creating list proxies */
057 private Constructor _listProxyConstructor;
058 /** The constructor used for creating set proxies */
059 private Constructor _setProxyConstructor;
060 /** The constructor used for creating collection proxies */
061 private Constructor _collectionProxyConstructor;
062
063 private static ProxyConfiguration getProxyConfiguration()
064 {
065 return (ProxyConfiguration) OjbConfigurator.getInstance().getConfigurationFor(null);
066 }
067
068 /**
069 * Returns the constructor of the indirection handler class.
070 *
071 * @return The constructor for indirection handlers
072 */
073 private synchronized Constructor getIndirectionHandlerConstructor()
074 {
075 if(_indirectionHandlerConstructor == null)
076 {
077 Class[] paramType = {PBKey.class, Identity.class};
078
079 try
080 {
081 _indirectionHandlerConstructor = getIndirectionHandlerClass().getConstructor(paramType);
082 }
083 catch(NoSuchMethodException ex)
084 {
085 throw new MetadataException("The class "
086 + _indirectionHandlerClass.getName()
087 + " specified for IndirectionHandlerClass"
088 + " is required to have a public constructor with signature ("
089 + PBKey.class.getName()
090 + ", "
091 + Identity.class.getName()
092 + ").");
093 }
094 }
095 return _indirectionHandlerConstructor;
096 }
097
098 /**
099 * Returns the indirection handler class.
100 *
101 * @return The class for indirection handlers
102 */
103 public Class getIndirectionHandlerClass()
104 {
105 if(_indirectionHandlerClass == null)
106 {
107 setIndirectionHandlerClass(getProxyConfiguration().getIndirectionHandlerClass());
108 }
109
110 return _indirectionHandlerClass;
111 }
112
113 /**
114 * Sets the indirection handler class.
115 *
116 * @param indirectionHandlerClass The class for indirection handlers
117 */
118 public void setIndirectionHandlerClass(Class indirectionHandlerClass)
119 {
120 if(indirectionHandlerClass == null)
121 {
122 //throw new MetadataException("No IndirectionHandlerClass specified.");
123 /**
124 * andrew.clute
125 * Allow the default IndirectionHandler for the given ProxyFactory implementation
126 * when the parameter is not given
127 */
128 indirectionHandlerClass = getDefaultIndirectionHandlerClass();
129 }
130 if(indirectionHandlerClass.isInterface()
131 || Modifier.isAbstract(indirectionHandlerClass.getModifiers())
132 || !getIndirectionHandlerBaseClass().isAssignableFrom(indirectionHandlerClass))
133 {
134 throw new MetadataException("Illegal class "
135 + indirectionHandlerClass.getName()
136 + " specified for IndirectionHandlerClass. Must be a concrete subclass of "
137 + getIndirectionHandlerBaseClass().getName());
138 }
139 _indirectionHandlerClass = indirectionHandlerClass;
140 }
141
142 /**
143 * Creates a new indirection handler instance.
144 *
145 * @param brokerKey The associated {@link PBKey}.
146 * @param id The subject's ids
147 * @return The new instance
148 */
149 public IndirectionHandler createIndirectionHandler(PBKey brokerKey, Identity id)
150 {
151 Object args[] = {brokerKey, id};
152
153 try
154 {
155 return (IndirectionHandler) getIndirectionHandlerConstructor().newInstance(args);
156 }
157 catch(InvocationTargetException ex)
158 {
159 throw new PersistenceBrokerException("Exception while creating a new indirection handler instance", ex);
160 }
161 catch(InstantiationException ex)
162 {
163 throw new PersistenceBrokerException("Exception while creating a new indirection handler instance", ex);
164 }
165 catch(IllegalAccessException ex)
166 {
167 throw new PersistenceBrokerException("Exception while creating a new indirection handler instance", ex);
168 }
169 }
170
171 /**
172 * Retrieves the constructor that is used by OJB to create instances of the given collection proxy
173 * class.
174 *
175 * @param proxyClass The proxy class
176 * @param baseType The required base type of the proxy class
177 * @param typeDesc The type of collection proxy
178 * @return The constructor
179 */
180 private static Constructor retrieveCollectionProxyConstructor(Class proxyClass, Class baseType, String typeDesc)
181 {
182 if(proxyClass == null)
183 {
184 throw new MetadataException("No " + typeDesc + " specified.");
185 }
186 if(proxyClass.isInterface() || Modifier.isAbstract(proxyClass.getModifiers()) || !baseType.isAssignableFrom(proxyClass))
187 {
188 throw new MetadataException("Illegal class "
189 + proxyClass.getName()
190 + " specified for "
191 + typeDesc
192 + ". Must be a concrete subclass of "
193 + baseType.getName());
194 }
195
196 Class[] paramType = {PBKey.class, Class.class, Query.class};
197
198 try
199 {
200 return proxyClass.getConstructor(paramType);
201 }
202 catch(NoSuchMethodException ex)
203 {
204 throw new MetadataException("The class "
205 + proxyClass.getName()
206 + " specified for "
207 + typeDesc
208 + " is required to have a public constructor with signature ("
209 + PBKey.class.getName()
210 + ", "
211 + Class.class.getName()
212 + ", "
213 + Query.class.getName()
214 + ").");
215 }
216 }
217
218 /**
219 * Returns the list proxy class.
220 *
221 * @return The class used for list proxies
222 */
223 public Class getListProxyClass()
224 {
225 return getListProxyConstructor().getDeclaringClass();
226 }
227
228 /**
229 * Returns the constructor of the list proxy class.
230 *
231 * @return The constructor for list proxies
232 */
233 private Constructor getListProxyConstructor()
234 {
235 if(_listProxyConstructor == null)
236 {
237 setListProxyClass(getProxyConfiguration().getListProxyClass());
238 }
239 return _listProxyConstructor;
240 }
241
242 /**
243 * Dets the proxy class to use for collection classes that implement the {@link java.util.List} interface.
244 * Notes that the proxy class must implement the {@link java.util.List} interface, and have a constructor
245 * of the signature ({@link org.apache.ojb.broker.PBKey}, {@link java.lang.Class}, {@link org.apache.ojb.broker.query.Query}).
246 *
247 * @param listProxyClass The proxy class
248 */
249 public void setListProxyClass(Class listProxyClass)
250 {
251 _listProxyConstructor = retrieveCollectionProxyConstructor(listProxyClass, List.class, "ListProxyClass");
252 }
253
254 /**
255 * Returns the set proxy class.
256 *
257 * @return The class used for set proxies
258 */
259 public Class getSetProxyClass()
260 {
261 return getSetProxyConstructor().getDeclaringClass();
262 }
263
264 /**
265 * Returns the constructor of the set proxy class.
266 *
267 * @return The constructor for set proxies
268 */
269 private Constructor getSetProxyConstructor()
270 {
271 if(_setProxyConstructor == null)
272 {
273 setSetProxyClass(getProxyConfiguration().getSetProxyClass());
274 }
275 return _setProxyConstructor;
276 }
277
278 /**
279 * Dets the proxy class to use for collection classes that implement the {@link Set} interface.
280 *
281 * @param setProxyClass The proxy class
282 */
283 public void setSetProxyClass(Class setProxyClass)
284 {
285 _setProxyConstructor = retrieveCollectionProxyConstructor(setProxyClass, Set.class, "SetProxyClass");
286 }
287
288 /**
289 * Returns the collection proxy class.
290 *
291 * @return The class used for collection proxies
292 */
293 public Class getCollectionProxyClass()
294 {
295 return getCollectionProxyConstructor().getDeclaringClass();
296 }
297
298 /**
299 * Returns the constructor of the generic collection proxy class.
300 *
301 * @return The constructor for collection proxies
302 */
303 private Constructor getCollectionProxyConstructor()
304 {
305 if(_collectionProxyConstructor == null)
306 {
307 setCollectionProxyClass(getProxyConfiguration().getCollectionProxyClass());
308 }
309 return _collectionProxyConstructor;
310 }
311
312 /**
313 * Dets the proxy class to use for generic collection classes implementing the {@link java.util.Collection} interface.
314 *
315 * @param collectionProxyClass The proxy class
316 */
317 public void setCollectionProxyClass(Class collectionProxyClass)
318 {
319 _collectionProxyConstructor = retrieveCollectionProxyConstructor(collectionProxyClass, Collection.class, "CollectionProxyClass");
320 // we also require the class to be a subclass of ManageableCollection
321 if(!ManageableCollection.class.isAssignableFrom(collectionProxyClass))
322 {
323 throw new MetadataException("Illegal class "
324 + collectionProxyClass.getName()
325 + " specified for CollectionProxyClass. Must be a concrete subclass of "
326 + ManageableCollection.class.getName());
327 }
328 }
329
330 /**
331 * Determines which proxy to use for the given collection class (list, set or generic collection proxy).
332 *
333 * @param collectionClass The collection class
334 * @return The constructor of the proxy class
335 */
336 private Constructor getCollectionProxyConstructor(Class collectionClass)
337 {
338 if(List.class.isAssignableFrom(collectionClass))
339 {
340 return getListProxyConstructor();
341 }
342 else if(Set.class.isAssignableFrom(collectionClass))
343 {
344 return getSetProxyConstructor();
345 }
346 else
347 {
348 return getCollectionProxyConstructor();
349 }
350 }
351
352 /**
353 * Create a Collection Proxy for a given query.
354 *
355 * @param brokerKey The key of the persistence broker
356 * @param query The query
357 * @param collectionClass The class to build the proxy for
358 * @return The collection proxy
359 */
360 public ManageableCollection createCollectionProxy(PBKey brokerKey, Query query, Class collectionClass)
361 {
362 Object args[] = {brokerKey, collectionClass, query};
363
364 try
365 {
366 return (ManageableCollection) getCollectionProxyConstructor(collectionClass).newInstance(args);
367 }
368 catch(InstantiationException ex)
369 {
370 throw new PersistenceBrokerException("Exception while creating a new collection proxy instance", ex);
371 }
372 catch(InvocationTargetException ex)
373 {
374 throw new PersistenceBrokerException("Exception while creating a new collection proxy instance", ex);
375 }
376 catch(IllegalAccessException ex)
377 {
378 throw new PersistenceBrokerException("Exception while creating a new collection proxy instance", ex);
379 }
380 }
381
382 /**
383 * Get the real Object
384 *
385 * @param objectOrProxy
386 * @return Object
387 */
388 public final Object getRealObject(Object objectOrProxy)
389 {
390 if(isNormalOjbProxy(objectOrProxy))
391 {
392 String msg;
393
394 try
395 {
396 return getIndirectionHandler(objectOrProxy).getRealSubject();
397 }
398 catch(ClassCastException e)
399 {
400 // shouldn't happen but still ...
401 msg = "The InvocationHandler for the provided Proxy was not an instance of " + IndirectionHandler.class.getName();
402 log.error(msg);
403 throw new PersistenceBrokerException(msg, e);
404 }
405 catch(IllegalArgumentException e)
406 {
407 msg = "Could not retrieve real object for given Proxy: " + objectOrProxy;
408 log.error(msg);
409 throw new PersistenceBrokerException(msg, e);
410 }
411 catch(PersistenceBrokerException e)
412 {
413 log.error("Could not retrieve real object for given Proxy: " + objectOrProxy);
414 throw e;
415 }
416 }
417 else if(isVirtualOjbProxy(objectOrProxy))
418 {
419 try
420 {
421 return ((VirtualProxy) objectOrProxy).getRealSubject();
422 }
423 catch(PersistenceBrokerException e)
424 {
425 log.error("Could not retrieve real object for VirtualProxy: " + objectOrProxy);
426 throw e;
427 }
428 }
429 else
430 {
431 return objectOrProxy;
432 }
433 }
434
435 /**
436 * Get the real Object for already materialized Handler
437 *
438 * @param objectOrProxy
439 * @return Object or null if the Handel is not materialized
440 */
441 public Object getRealObjectIfMaterialized(Object objectOrProxy)
442 {
443 if(isNormalOjbProxy(objectOrProxy))
444 {
445 String msg;
446
447 try
448 {
449 IndirectionHandler handler = getIndirectionHandler(objectOrProxy);
450
451 return handler.alreadyMaterialized() ? handler.getRealSubject() : null;
452 }
453 catch(ClassCastException e)
454 {
455 // shouldn't happen but still ...
456 msg = "The InvocationHandler for the provided Proxy was not an instance of " + IndirectionHandler.class.getName();
457 log.error(msg);
458 throw new PersistenceBrokerException(msg, e);
459 }
460 catch(IllegalArgumentException e)
461 {
462 msg = "Could not retrieve real object for given Proxy: " + objectOrProxy;
463 log.error(msg);
464 throw new PersistenceBrokerException(msg, e);
465 }
466 catch(PersistenceBrokerException e)
467 {
468 log.error("Could not retrieve real object for given Proxy: " + objectOrProxy);
469 throw e;
470 }
471 }
472 else if(isVirtualOjbProxy(objectOrProxy))
473 {
474 try
475 {
476 VirtualProxy proxy = (VirtualProxy) objectOrProxy;
477
478 return proxy.alreadyMaterialized() ? proxy.getRealSubject() : null;
479 }
480 catch(PersistenceBrokerException e)
481 {
482 log.error("Could not retrieve real object for VirtualProxy: " + objectOrProxy);
483 throw e;
484 }
485 }
486 else
487 {
488 return objectOrProxy;
489 }
490 }
491
492 /**
493 * Get the real Class
494 *
495 * @param objectOrProxy
496 * @return Class
497 */
498 public Class getRealClass(Object objectOrProxy)
499 {
500 IndirectionHandler handler;
501
502 if(isNormalOjbProxy(objectOrProxy))
503 {
504 String msg;
505
506 try
507 {
508 handler = getIndirectionHandler(objectOrProxy);
509 /*
510 arminw:
511 think we should return the real class
512 */
513 // return handler.getIdentity().getObjectsTopLevelClass();
514 return handler.getIdentity().getObjectsRealClass();
515 }
516 catch(ClassCastException e)
517 {
518 // shouldn't happen but still ...
519 msg = "The InvocationHandler for the provided Proxy was not an instance of " + IndirectionHandler.class.getName();
520 log.error(msg);
521 throw new PersistenceBrokerException(msg, e);
522 }
523 catch(IllegalArgumentException e)
524 {
525 msg = "Could not retrieve real object for given Proxy: " + objectOrProxy;
526 log.error(msg);
527 throw new PersistenceBrokerException(msg, e);
528 }
529 }
530 else if(isVirtualOjbProxy(objectOrProxy))
531 {
532 handler = VirtualProxy.getIndirectionHandler((VirtualProxy) objectOrProxy);
533 /*
534 arminw:
535 think we should return the real class
536 */
537 // return handler.getIdentity().getObjectsTopLevelClass();
538 return handler.getIdentity().getObjectsRealClass();
539 }
540 else
541 {
542 return objectOrProxy.getClass();
543 }
544 }
545
546 /**
547 * Determines whether the given object is an OJB proxy.
548 *
549 * @return <code>true</code> if the object is an OJB proxy
550 */
551 public boolean isNormalOjbProxy(Object proxyOrObject)
552 {
553 return proxyOrObject instanceof OJBProxy;
554 }
555
556 /**
557 * Determines whether the given object is an OJB virtual proxy.
558 *
559 * @return <code>true</code> if the object is an OJB virtual proxy
560 */
561 public boolean isVirtualOjbProxy(Object proxyOrObject)
562 {
563 return proxyOrObject instanceof VirtualProxy;
564 }
565
566 /**
567 * Returns <tt>true</tt> if the given object is a {@link java.lang.reflect.Proxy}
568 * or a {@link VirtualProxy} instance.
569 */
570 public boolean isProxy(Object proxyOrObject)
571 {
572 return isNormalOjbProxy(proxyOrObject) || isVirtualOjbProxy(proxyOrObject);
573 }
574
575 /**
576 * Returns the IndirectionHandler associated with a dynamic proxy. Each
577 * subclass is responsible for it's execution
578 */
579 protected abstract IndirectionHandler getDynamicIndirectionHandler(Object obj);
580
581 /**
582 * Returns the invocation handler object of the given proxy object.
583 *
584 * @param obj The object
585 * @return The invocation handler if the object is an OJB proxy, or <code>null</code>
586 * otherwise
587 */
588 public IndirectionHandler getIndirectionHandler(Object obj)
589 {
590 if(obj == null)
591 {
592 return null;
593 }
594 else if(isNormalOjbProxy(obj))
595 {
596 return getDynamicIndirectionHandler(obj);
597 }
598 else if(isVirtualOjbProxy(obj))
599 {
600 return VirtualProxy.getIndirectionHandler((VirtualProxy) obj);
601 }
602 else
603 {
604 return null;
605 }
606
607 }
608
609 /**
610 * Determines whether the object is a materialized object, i.e. no proxy or a
611 * proxy that has already been loaded from the database.
612 *
613 * @param object The object to test
614 * @return <code>true</code> if the object is materialized
615 */
616 public boolean isMaterialized(Object object)
617 {
618 IndirectionHandler handler = getIndirectionHandler(object);
619
620 return handler == null || handler.alreadyMaterialized();
621 }
622
623 /** Return CollectionProxy for item is item is a CollectionProxy, otherwise return null */
624 public CollectionProxy getCollectionProxy(Object item)
625 {
626 if(isCollectionProxy(item))
627 {
628 return (CollectionProxy) item;
629 }
630 else
631 {
632 return null;
633 }
634 }
635
636 /**
637 * Reports if item is a CollectionProxy.
638 *
639 * TODO: Provide handling for pluggable collection proxy implementations
640 */
641 public boolean isCollectionProxy(Object item)
642 {
643 return (item instanceof CollectionProxy);
644 }
645
646 /**
647 * Materialization-safe version of toString. If the object is a yet-unmaterialized proxy,
648 * then only the text "unmaterialized proxy for ..." is returned and the proxy is NOT
649 * materialized. Otherwise, the normal toString method is called. This useful e.g. for
650 * logging etc.
651 *
652 * @param proxy The object for which a string representation shall be generated
653 * @return The string representation
654 */
655 public String toString(Object proxy)
656 {
657 IndirectionHandler handler = getIndirectionHandler(proxy);
658 if((handler != null) && handler.alreadyMaterialized())
659 {
660 return "unmaterialized proxy for " + handler.getIdentity();
661 }
662 else
663 {
664 return proxy.toString();
665 }
666 }
667
668 public synchronized static ProxyFactory getProxyFactory()
669 {
670 /*
671 TODO: Check usage of singleton.
672 arminw: Think the ProxyFactory instance can be a singleton, because the proxy stuff
673 will never change while runtime.
674 */
675 if(singleton == null)
676 {
677 Class proxyFactoryClass = null;
678 try
679 {
680 proxyFactoryClass = getProxyConfiguration().getProxyFactoryClass();
681 singleton = (ProxyFactory) proxyFactoryClass.newInstance();
682 }
683 catch(InstantiationException e)
684 {
685 throw new MetadataException("Illegal class " + proxyFactoryClass.getName() + " specified for ProxyFactoryClass.");
686 }
687 catch(IllegalAccessException e)
688 {
689 throw new MetadataException("Illegal class " + proxyFactoryClass.getName() + " specified for ProxyFactoryClass.");
690 }
691 }
692 return singleton;
693 }
694
695 }