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