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 org.apache.ojb.broker.*;
019    import org.apache.ojb.broker.core.PersistenceBrokerThreadMapping;
020    import org.apache.ojb.broker.util.logging.LoggerFactory;
021    
022    import java.lang.reflect.Method;
023    import java.util.ArrayList;
024    
025    /**
026     * Abstract implementation for the indirection handler used by ojb's proxies.
027     *
028     * @version $Id: AbstractIndirectionHandler.java,v 1.1 2007-08-24 22:17:32 ewestfal Exp $
029     */
030    public abstract class AbstractIndirectionHandler implements IndirectionHandler
031    {
032        static final long serialVersionUID = -1993879565033755826L;
033    
034        /** The key for acquiring the above broker */
035        private PBKey _brokerKey;
036        /** The real subject which this is hidden by the proxy */
037        private Object _realSubject = null;
038        /** Represents the identity of the real subject. When the real subject is not
039         *  yet materialized, it can be loaded from the underlying db by this identity object */
040        private Identity _id = null;
041        /** The materialization listeners */
042        private transient ArrayList _listeners;
043    
044            /**
045             * Creates a new indirection handler for the indicated object.
046             *
047             * @param brokerKey
048             *            The key of the persistence broker
049             * @param id
050             *            The identity of the subject
051             */
052            public AbstractIndirectionHandler(PBKey brokerKey, Identity id)
053            {
054                    setBrokerKey(brokerKey);
055                    setIdentity(id);
056            }
057    
058            /**
059             * Returns the identity of the subject.
060             *
061             * @return The identity
062             */
063            public Identity getIdentity()
064            {
065                    return _id;
066            }
067    
068            /**
069             * Sets the identity of the subject of this indirection handler.
070             *
071             * @param identity
072             */
073            protected void setIdentity(Identity identity)
074            {
075                    _id = identity;
076            }
077    
078            /**
079             * Returns the key of the persistence broker used by this indirection
080             * handler.
081             *
082             * @return The broker key
083             */
084            public PBKey getBrokerKey()
085            {
086                    return _brokerKey;
087            }
088    
089            /**
090             * Sets the key of the persistence broker used by this indirection handler.
091             *
092             * @param brokerKey
093             *            The broker key
094             */
095            protected void setBrokerKey(PBKey brokerKey)
096            {
097                    _brokerKey = brokerKey;
098            }
099    
100            /**
101             * Adds a materialization listener.
102             *
103             * @param listener
104             *            The listener to add
105             */
106            public synchronized void addListener(MaterializationListener listener)
107            {
108                    if (_listeners == null)
109                    {
110                            _listeners = new ArrayList();
111                    }
112                    // add listener only once
113                    if (!_listeners.contains(listener))
114                    {
115                            _listeners.add(listener);
116                    }
117            }
118    
119            /**
120             * Removes a materialization listener.
121             *
122             * @param listener
123             *            The listener to remove
124             */
125            public synchronized void removeListener(MaterializationListener listener)
126            {
127                    if (_listeners != null)
128                    {
129                            _listeners.remove(listener);
130                    }
131            }
132    
133            /**
134             * Calls beforeMaterialization on all registered listeners in the reverse
135             * order of registration.
136             */
137            protected void beforeMaterialization()
138            {
139                    if (_listeners != null)
140                    {
141                            MaterializationListener listener;
142    
143                            for (int idx = _listeners.size() - 1; idx >= 0; idx--)
144                            {
145                                    listener = (MaterializationListener) _listeners.get(idx);
146                                    listener.beforeMaterialization(this, _id);
147                            }
148                    }
149            }
150    
151            /**
152             * Calls afterMaterialization on all registered listeners in the reverse
153             * order of registration.
154             */
155            protected void afterMaterialization()
156            {
157                    if (_listeners != null)
158                    {
159                            MaterializationListener listener;
160    
161                            // listeners may remove themselves during the afterMaterialization
162                            // callback.
163                            // thus we must iterate through the listeners vector from back to
164                            // front
165                            // to avoid index problems.
166                            for (int idx = _listeners.size() - 1; idx >= 0; idx--)
167                            {
168                                    listener = (MaterializationListener) _listeners.get(idx);
169                                    listener.afterMaterialization(this, _realSubject);
170                            }
171                    }
172            }
173    
174        /**
175         * Gets the persistence broker used by this indirection handler.
176         * If no PBKey is available a runtime exception will be thrown.
177         *
178         * @return a PersistenceBroker
179         */
180        protected TemporaryBrokerWrapper getBroker() throws PBFactoryException
181        {
182            PersistenceBrokerInternal broker;
183            boolean needsClose = false;
184    
185            if (getBrokerKey() == null)
186            {
187                /*
188                arminw:
189                if no PBKey is set we throw an exception, because we don't
190                know which PB (connection) should be used.
191                */
192                throw new OJBRuntimeException("Can't find associated PBKey. Need PBKey to obtain a valid" +
193                                              "PersistenceBroker instance from intern resources.");
194            }
195            // first try to use the current threaded broker to avoid blocking
196            broker = PersistenceBrokerThreadMapping.currentPersistenceBroker(getBrokerKey());
197            // current broker not found, create a intern new one
198            if ((broker == null) || broker.isClosed())
199            {
200                broker = (PersistenceBrokerInternal) PersistenceBrokerFactory.createPersistenceBroker(getBrokerKey());
201                /** Specifies whether we obtained a fresh broker which we have to close after we used it */
202                needsClose = true;
203            }
204            return new TemporaryBrokerWrapper(broker, needsClose);
205        }
206    
207            /**
208             * [Copied from {@link java.lang.reflect.InvocationHandler}]:<br/>
209             * Processes a method invocation on a proxy instance and returns the result.
210             * This method will be invoked on an invocation handler when a method is
211             * invoked on a proxy instance that it is associated with.
212             *
213             * @param proxy
214             *            The proxy instance that the method was invoked on
215             *
216             * @param method
217             *            The <code>Method</code> instance corresponding to the
218             *            interface method invoked on the proxy instance. The declaring
219             *            class of the <code>Method</code> object will be the
220             *            interface that the method was declared in, which may be a
221             *            superinterface of the proxy interface that the proxy class
222             *            inherits the method through.
223             *
224             * @param args
225             *            An array of objects containing the values of the arguments
226             *            passed in the method invocation on the proxy instance, or
227             *            <code>null</code> if interface method takes no arguments.
228             *            Arguments of primitive types are wrapped in instances of the
229             *            appropriate primitive wrapper class, such as
230             *            <code>java.lang.Integer</code> or
231             *            <code>java.lang.Boolean</code>.
232             *
233             * @return The value to return from the method invocation on the proxy
234             *         instance. If the declared return type of the interface method is
235             *         a primitive type, then the value returned by this method must be
236             *         an instance of the corresponding primitive wrapper class;
237             *         otherwise, it must be a type assignable to the declared return
238             *         type. If the value returned by this method is <code>null</code>
239             *         and the interface method's return type is primitive, then a
240             *         <code>NullPointerException</code> will be thrown by the method
241             *         invocation on the proxy instance. If the value returned by this
242             *         method is otherwise not compatible with the interface method's
243             *         declared return type as described above, a
244             *         <code>ClassCastException</code> will be thrown by the method
245             *         invocation on the proxy instance.
246             *
247             * @throws PersistenceBrokerException
248             *             The exception to throw from the method invocation on the
249             *             proxy instance. The exception's type must be assignable
250             *             either to any of the exception types declared in the
251             *             <code>throws</code> clause of the interface method or to
252             *             the unchecked exception types
253             *             <code>java.lang.RuntimeException</code> or
254             *             <code>java.lang.Error</code>. If a checked exception is
255             *             thrown by this method that is not assignable to any of the
256             *             exception types declared in the <code>throws</code> clause
257             *             of the interface method, then an
258             *             {@link java.lang.reflect.UndeclaredThrowableException}
259             *             containing the exception that was thrown by this method will
260             *             be thrown by the method invocation on the proxy instance.
261             *
262             * @see java.lang.reflect.UndeclaredThrowableException
263             */
264            public Object invoke(Object proxy, Method method, Object[] args)
265            {
266                    Object subject;
267                    String methodName = method.getName();
268    
269                    try
270                    {
271                            // [andrew clute]
272                            // short-circuit any calls to a finalize methjod if the subject
273                            // has not been retrieved yet
274                            if ("finalize".equals(methodName) && _realSubject == null)
275                            {
276                                    return null;
277                            }
278    
279                            // [andrew clute]
280                            // When trying to serialize a proxy, we need to determine how to
281                            // handle it
282                            if ("writeReplace".equals(methodName))
283                            {
284                                    if (_realSubject == null)
285                                    {
286                                            // Unmaterialized proxies are replaced by simple
287                                            // serializable
288                                            // objects that can be unserialized without classloader
289                                            // issues
290                                            return generateSerializableProxy();
291                                    } else
292                                    {
293                                            // Materiliazed objects should be passed back as they might
294                                            // have
295                                            // been mutated
296                                            return getRealSubject();
297                                    }
298                            }
299    
300                            // [tomdz]
301                            // Previously the hashcode of the identity would have been used
302                            // but this requires a compatible hashCode implementation in the
303                            // proxied object (which is somewhat unlikely, even the default
304                            // hashCode implementation does not fulfill this requirement)
305                            // for those that require this behavior, a custom indirection
306                            // handler can be used, or the hashCode of the identity
307                            /*
308                             * if ("hashCode".equals(methodName)) { return new
309                             * Integer(_id.hashCode()); }
310                             */
311    
312                            // [tomdz]
313                            // this would handle toString differently for non-materialized
314                            // proxies
315                            // (to avoid materialization due to logging)
316                            // however toString should be a normal business method which
317                            // materializes the proxy
318                            // if this is not desired, then the ProxyHandler.toString(Object)
319                            // method
320                            // should be used instead (e.g. for logging within OJB)
321                            /*
322                             * if ((realSubject == null) && "toString".equals(methodName)) {
323                             * return "unmaterialized proxy for " + id; }
324                             */
325    
326                            // BRJ: make sure that the object to be compared is a real object
327                            // otherwise equals may return false.
328                            if ("equals".equals(methodName) && args[0] != null)
329                            {
330                    TemporaryBrokerWrapper tmp = getBroker();
331                    try
332                    {
333                        args[0] = tmp.broker.getProxyFactory().getRealObject(args[0]);
334                    }
335                    finally
336                    {
337                        tmp.close();
338                    }
339                }
340    
341                            if ("getIndirectionHandler".equals(methodName) && args[0] != null)
342                            {
343                                    return this;
344                            }
345    
346                            subject = getRealSubject();
347    
348                //kuali modification start
349                            try {
350                    method.setAccessible(true);
351                } catch (SecurityException ex) {
352                    LoggerFactory.getLogger(IndirectionHandler.class).warn(
353                            "Error calling setAccessible for method " + method.getName(), ex);
354                }
355                //kuali modification end
356    
357                            return method.invoke(subject, args);
358                            // [olegnitz] I've changed the following strange lines
359                            // to the above one. Why was this done in such complicated way?
360                            // Is it possible that subject doesn't implement the method's
361                            // interface?
362                            // Method m = subject.getClass().getMethod(method.getName(),
363                            // method.getParameterTypes());
364                            // return m.invoke(subject, args);
365                    } catch (Exception ex)
366                    {
367                            throw new PersistenceBrokerException("Error invoking method " + method.getName(), ex);
368                    }
369            }
370    
371            /**
372             * Returns the proxies real subject. The subject will be materialized if
373             * necessary.
374             *
375             * @return The subject
376             */
377            public Object getRealSubject() throws PersistenceBrokerException
378            {
379                    if (_realSubject == null)
380                    {
381                            beforeMaterialization();
382                            _realSubject = materializeSubject();
383                            afterMaterialization();
384                    }
385                    return _realSubject;
386            }
387    
388            /**
389             * [olegnitz] This looks stupid, but is really necessary for OTM: the
390             * materialization listener replaces the real subject by its clone to ensure
391             * transaction isolation. Is there a better way to do this?
392             */
393            public void setRealSubject(Object object)
394            {
395                    _realSubject = object;
396            }
397    
398            /**
399             * Retrieves the real subject from the underlying RDBMS. Override this
400             * method if the object is to be materialized in a specific way.
401             *
402             * @return The real subject of the proxy
403             */
404            protected synchronized Object materializeSubject() throws PersistenceBrokerException
405            {
406                    TemporaryBrokerWrapper tmp = getBroker();
407            try
408                    {
409                            Object realSubject = tmp.broker.getObjectByIdentity(_id);
410                            if (realSubject == null)
411                            {
412                                    LoggerFactory.getLogger(IndirectionHandler.class).warn(
413                                                    "Can not materialize object for Identity " + _id + " - using PBKey " + getBrokerKey());
414                            }
415                            return realSubject;
416                    } catch (Exception ex)
417                    {
418                            throw new PersistenceBrokerException(ex);
419                    } finally
420                    {
421                            tmp.close();
422                    }
423            }
424    
425            /**
426             * Determines whether the real subject already has been materialized.
427             *
428             * @return <code>true</code> if the real subject has already been loaded
429             */
430            public boolean alreadyMaterialized()
431            {
432                    return _realSubject != null;
433            }
434    
435            /**
436             * Generate a simple object that is serializable and placeholder for
437             * proxies.
438             *
439             */
440            private Object generateSerializableProxy()
441            {
442                    return new OJBSerializableProxy(getIdentity().getObjectsRealClass(), this);
443            }
444    
445        //===================================================================
446        // inner class
447        //===================================================================
448        /**
449         * wrapper class for temporary used broker instances.
450         */
451        static final class TemporaryBrokerWrapper
452        {
453            /** Specifies whether we obtained a fresh broker which we have to close after we used it */
454            boolean needsClose;
455            PersistenceBrokerInternal broker;
456    
457            public TemporaryBrokerWrapper(PersistenceBrokerInternal broker, boolean needsClose)
458            {
459                this.broker = broker;
460                this.needsClose = needsClose;
461            }
462    
463            /**
464             * Cleanup the used broker instance, it's mandatory to call
465             * this method after use.
466             */
467            public void close()
468            {
469                if(needsClose)
470                {
471                    broker.close();
472                }
473            }
474        }
475    }