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 }