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    }