Author: arminw Date: Thu Jan 4 19:04:34 2007 New Revision: 492879 URL: http://svn.apache.org/viewvc?view=rev&rev=492879 Log: fix NPE issues with not existing objects of PK/FK. This could be the case if a PK of an object is the FK to a 1:1 reference. If the reference is deleted the PK of the main object still exists and OJB will create a proxy object for the 1:1 reference. On materialization of the proxy OJB can't find the reference, thus we return 'null' for all object methods and log a warn message.
Modified: db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java Modified: db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java URL: http://svn.apache.org/viewvc/db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java?view=diff&rev=492879&r1=492878&r2=492879 ============================================================================== --- db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java (original) +++ db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java Thu Jan 4 19:04:34 2007 @@ -26,9 +26,9 @@ import org.apache.ojb.broker.PersistenceBrokerFactory; import org.apache.ojb.broker.PersistenceBrokerInternal; import org.apache.ojb.broker.core.PersistenceBrokerThreadMapping; -import org.apache.ojb.broker.metadata.MetadataException; import org.apache.ojb.broker.metadata.MetadataManager; import org.apache.ojb.broker.util.logging.LoggerFactory; +import org.apache.ojb.broker.util.logging.Logger; /** * Abstract implementation for the indirection handler used by ojb's proxies. @@ -37,6 +37,7 @@ */ public abstract class AbstractIndirectionHandler implements IndirectionHandler { + private static Logger log = LoggerFactory.getLogger(AbstractIndirectionHandler.class); private static final long serialVersionUID = 2L; @@ -54,19 +55,20 @@ /** The materialization listeners */ private transient ArrayList _listeners; - /** - * Creates a new indirection handler for the indicated object. - * - * @param brokerKey - * The key of the persistence broker - * @param id - * The identity of the subject - */ - public AbstractIndirectionHandler(PBKey brokerKey, Identity id) - { - setBrokerKey(brokerKey); - setIdentity(id); - } + /** + * Creates a new indirection handler for the indicated object. + * + * @param brokerKey + * The key of the persistence broker + * @param id + * The identity of the subject + */ + public AbstractIndirectionHandler(PBKey brokerKey, Identity id) + { + setBrokerKey(brokerKey); + setIdentity(id); + _perThreadDescriptorsEnabled = MetadataManager.getInstance().isEnablePerThreadChanges(); + } /** * Reactivates metadata profile used when creating proxy, if needed. @@ -87,126 +89,126 @@ } } - /** - * Returns the identity of the subject. - * - * @return The identity - */ - public Identity getIdentity() - { - return _id; - } - - /** - * Sets the identity of the subject of this indirection handler. - * - * @param identity - */ - protected void setIdentity(Identity identity) - { - _id = identity; - } - - /** - * Returns the key of the persistence broker used by this indirection - * handler. - * - * @return The broker key - */ - public PBKey getBrokerKey() - { - return _brokerKey; - } - - /** - * Sets the key of the persistence broker used by this indirection handler. - * - * @param brokerKey - * The broker key - */ - protected void setBrokerKey(PBKey brokerKey) - { - _brokerKey = brokerKey; - } - - /** - * Adds a materialization listener. - * - * @param listener - * The listener to add - */ - public synchronized void addListener(MaterializationListener listener) - { - if (_listeners == null) - { - _listeners = new ArrayList(); - } - // add listener only once - if (!_listeners.contains(listener)) - { - _listeners.add(listener); - } - } - - /** - * Removes a materialization listener. - * - * @param listener - * The listener to remove - */ - public synchronized void removeListener(MaterializationListener listener) - { - if (_listeners != null) - { - _listeners.remove(listener); - } - } - - /** - * Calls beforeMaterialization on all registered listeners in the reverse - * order of registration. - */ - protected void beforeMaterialization() - { - if (_listeners != null) - { - MaterializationListener listener; + /** + * Returns the identity of the subject. + * + * @return The identity + */ + public Identity getIdentity() + { + return _id; + } + + /** + * Sets the identity of the subject of this indirection handler. + * + * @param identity + */ + protected void setIdentity(Identity identity) + { + _id = identity; + } + + /** + * Returns the key of the persistence broker used by this indirection + * handler. + * + * @return The broker key + */ + public PBKey getBrokerKey() + { + return _brokerKey; + } + + /** + * Sets the key of the persistence broker used by this indirection handler. + * + * @param brokerKey + * The broker key + */ + protected void setBrokerKey(PBKey brokerKey) + { + _brokerKey = brokerKey; + } + + /** + * Adds a materialization listener. + * + * @param listener + * The listener to add + */ + public synchronized void addListener(MaterializationListener listener) + { + if (_listeners == null) + { + _listeners = new ArrayList(); + } + // add listener only once + if (!_listeners.contains(listener)) + { + _listeners.add(listener); + } + } + + /** + * Removes a materialization listener. + * + * @param listener + * The listener to remove + */ + public synchronized void removeListener(MaterializationListener listener) + { + if (_listeners != null) + { + _listeners.remove(listener); + } + } + + /** + * Calls beforeMaterialization on all registered listeners in the reverse + * order of registration. + */ + protected void beforeMaterialization() + { + if (_listeners != null) + { + MaterializationListener listener; if (_perThreadDescriptorsEnabled) { loadProfileIfNeeded(); } - for (int idx = _listeners.size() - 1; idx >= 0; idx--) - { - listener = (MaterializationListener) _listeners.get(idx); - listener.beforeMaterialization(this, _id); - } - } - } - - /** - * Calls afterMaterialization on all registered listeners in the reverse - * order of registration. - */ - protected void afterMaterialization() - { - if (_listeners != null) - { - MaterializationListener listener; + for (int idx = _listeners.size() - 1; idx >= 0; idx--) + { + listener = (MaterializationListener) _listeners.get(idx); + listener.beforeMaterialization(this, _id); + } + } + } + + /** + * Calls afterMaterialization on all registered listeners in the reverse + * order of registration. + */ + protected void afterMaterialization() + { + if (_listeners != null) + { + MaterializationListener listener; if (_perThreadDescriptorsEnabled) { loadProfileIfNeeded(); } - // listeners may remove themselves during the afterMaterialization - // callback. - // thus we must iterate through the listeners vector from back to - // front to avoid index problems. - for (int idx = _listeners.size() - 1; idx >= 0; idx--) - { - listener = (MaterializationListener) _listeners.get(idx); - listener.afterMaterialization(this, _realSubject); - } - } - } + // listeners may remove themselves during the afterMaterialization + // callback. + // thus we must iterate through the listeners vector from back to + // front to avoid index problems. + for (int idx = _listeners.size() - 1; idx >= 0; idx--) + { + listener = (MaterializationListener) _listeners.get(idx); + listener.afterMaterialization(this, _realSubject); + } + } + } /** * Gets the persistence broker used by this indirection handler. @@ -231,7 +233,7 @@ if no PBKey is set we throw an exception, because we don't know which PB (connection) should be used. */ - throw new OJBRuntimeException("Can't find associated PBKey. Need PBKey to obtain a valid" + + throw new OJBRuntimeException("Can't find associated PBKey. Need PBKey to obtain a valid " + "PersistenceBroker instance from intern resources."); } // first try to use the current threaded broker to avoid blocking @@ -246,129 +248,129 @@ return new TemporaryBrokerWrapper(broker, needsClose); } - /** - * [Copied from [EMAIL PROTECTED] java.lang.reflect.InvocationHandler}]:<br/> - * Processes a method invocation on a proxy instance and returns the result. - * This method will be invoked on an invocation handler when a method is - * invoked on a proxy instance that it is associated with. - * - * @param proxy - * The proxy instance that the method was invoked on - * - * @param method - * The <code>Method</code> instance corresponding to the - * interface method invoked on the proxy instance. The declaring - * class of the <code>Method</code> object will be the - * interface that the method was declared in, which may be a - * superinterface of the proxy interface that the proxy class - * inherits the method through. - * - * @param args - * An array of objects containing the values of the arguments - * passed in the method invocation on the proxy instance, or - * <code>null</code> if interface method takes no arguments. - * Arguments of primitive types are wrapped in instances of the - * appropriate primitive wrapper class, such as - * <code>java.lang.Integer</code> or - * <code>java.lang.Boolean</code>. - * - * @return The value to return from the method invocation on the proxy - * instance. If the declared return type of the interface method is - * a primitive type, then the value returned by this method must be - * an instance of the corresponding primitive wrapper class; - * otherwise, it must be a type assignable to the declared return - * type. If the value returned by this method is <code>null</code> - * and the interface method's return type is primitive, then a - * <code>NullPointerException</code> will be thrown by the method - * invocation on the proxy instance. If the value returned by this - * method is otherwise not compatible with the interface method's - * declared return type as described above, a - * <code>ClassCastException</code> will be thrown by the method - * invocation on the proxy instance. - * - * @throws PersistenceBrokerException - * The exception to throw from the method invocation on the - * proxy instance. The exception's type must be assignable - * either to any of the exception types declared in the - * <code>throws</code> clause of the interface method or to - * the unchecked exception types - * <code>java.lang.RuntimeException</code> or - * <code>java.lang.Error</code>. If a checked exception is - * thrown by this method that is not assignable to any of the - * exception types declared in the <code>throws</code> clause - * of the interface method, then an - * [EMAIL PROTECTED] java.lang.reflect.UndeclaredThrowableException} - * containing the exception that was thrown by this method will - * be thrown by the method invocation on the proxy instance. - * - * @see java.lang.reflect.UndeclaredThrowableException - */ - public Object invoke(Object proxy, Method method, Object[] args) - { - Object subject; - String methodName = method.getName(); - - try - { - // [andrew clute] - // short-circuit any calls to a finalize methjod if the subject - // has not been retrieved yet - if ("finalize".equals(methodName) && _realSubject == null) - { - return null; - } - - // [andrew clute] - // When trying to serialize a proxy, we need to determine how to - // handle it - if ("writeReplace".equals(methodName)) - { - if (_realSubject == null) - { - // Unmaterialized proxies are replaced by simple - // serializable - // objects that can be unserialized without classloader - // issues - return generateSerializableProxy(); - } else - { - // Materiliazed objects should be passed back as they might - // have - // been mutated - return getRealSubject(); - } - } - - // [tomdz] - // Previously the hashcode of the identity would have been used - // but this requires a compatible hashCode implementation in the - // proxied object (which is somewhat unlikely, even the default - // hashCode implementation does not fulfill this requirement) - // for those that require this behavior, a custom indirection - // handler can be used, or the hashCode of the identity - /* - * if ("hashCode".equals(methodName)) { return new - * Integer(_id.hashCode()); } - */ - - // [tomdz] - // this would handle toString differently for non-materialized - // proxies - // (to avoid materialization due to logging) - // however toString should be a normal business method which - // materializes the proxy - // if this is not desired, then the ProxyHandler.toString(Object) - // method - // should be used instead (e.g. for logging within OJB) - /* - * if ((realSubject == null) && "toString".equals(methodName)) { - * return "unmaterialized proxy for " + id; } - */ - - // BRJ: make sure that the object to be compared is a real object - // otherwise equals may return false. - if ("equals".equals(methodName) && args[0] != null) - { + /** + * [Copied from [EMAIL PROTECTED] java.lang.reflect.InvocationHandler}]:<br/> + * Processes a method invocation on a proxy instance and returns the result. + * This method will be invoked on an invocation handler when a method is + * invoked on a proxy instance that it is associated with. + * + * @param proxy + * The proxy instance that the method was invoked on + * + * @param method + * The <code>Method</code> instance corresponding to the + * interface method invoked on the proxy instance. The declaring + * class of the <code>Method</code> object will be the + * interface that the method was declared in, which may be a + * superinterface of the proxy interface that the proxy class + * inherits the method through. + * + * @param args + * An array of objects containing the values of the arguments + * passed in the method invocation on the proxy instance, or + * <code>null</code> if interface method takes no arguments. + * Arguments of primitive types are wrapped in instances of the + * appropriate primitive wrapper class, such as + * <code>java.lang.Integer</code> or + * <code>java.lang.Boolean</code>. + * + * @return The value to return from the method invocation on the proxy + * instance. If the declared return type of the interface method is + * a primitive type, then the value returned by this method must be + * an instance of the corresponding primitive wrapper class; + * otherwise, it must be a type assignable to the declared return + * type. If the value returned by this method is <code>null</code> + * and the interface method's return type is primitive, then a + * <code>NullPointerException</code> will be thrown by the method + * invocation on the proxy instance. If the value returned by this + * method is otherwise not compatible with the interface method's + * declared return type as described above, a + * <code>ClassCastException</code> will be thrown by the method + * invocation on the proxy instance. + * + * @throws PersistenceBrokerException + * The exception to throw from the method invocation on the + * proxy instance. The exception's type must be assignable + * either to any of the exception types declared in the + * <code>throws</code> clause of the interface method or to + * the unchecked exception types + * <code>java.lang.RuntimeException</code> or + * <code>java.lang.Error</code>. If a checked exception is + * thrown by this method that is not assignable to any of the + * exception types declared in the <code>throws</code> clause + * of the interface method, then an + * [EMAIL PROTECTED] java.lang.reflect.UndeclaredThrowableException} + * containing the exception that was thrown by this method will + * be thrown by the method invocation on the proxy instance. + * + * @see java.lang.reflect.UndeclaredThrowableException + */ + public Object invoke(Object proxy, Method method, Object[] args) + { + Object subject; + String methodName = method.getName(); + + try + { + // [andrew clute] + // short-circuit any calls to a finalize methjod if the subject + // has not been retrieved yet + if ("finalize".equals(methodName) && _realSubject == null) + { + return null; + } + + // [andrew clute] + // When trying to serialize a proxy, we need to determine how to + // handle it + if ("writeReplace".equals(methodName)) + { + if (_realSubject == null) + { + // Unmaterialized proxies are replaced by simple + // serializable + // objects that can be unserialized without classloader + // issues + return generateSerializableProxy(); + } else + { + // Materiliazed objects should be passed back as they might + // have + // been mutated + return getRealSubject(); + } + } + + // [tomdz] + // Previously the hashcode of the identity would have been used + // but this requires a compatible hashCode implementation in the + // proxied object (which is somewhat unlikely, even the default + // hashCode implementation does not fulfill this requirement) + // for those that require this behavior, a custom indirection + // handler can be used, or the hashCode of the identity + /* + * if ("hashCode".equals(methodName)) { return new + * Integer(_id.hashCode()); } + */ + + // [tomdz] + // this would handle toString differently for non-materialized + // proxies + // (to avoid materialization due to logging) + // however toString should be a normal business method which + // materializes the proxy + // if this is not desired, then the ProxyHandler.toString(Object) + // method + // should be used instead (e.g. for logging within OJB) + /* + * if ((realSubject == null) && "toString".equals(methodName)) { + * return "unmaterialized proxy for " + id; } + */ + + // BRJ: make sure that the object to be compared is a real object + // otherwise equals may return false. + if ("equals".equals(methodName) && args[0] != null) + { TemporaryBrokerWrapper tmp = getBroker(); try { @@ -380,99 +382,115 @@ } } - if ("getIndirectionHandler".equals(methodName) && args[0] != null) - { - return this; - } - - subject = getRealSubject(); - return method.invoke(subject, args); - // [olegnitz] I've changed the following strange lines - // to the above one. Why was this done in such complicated way? - // Is it possible that subject doesn't implement the method's - // interface? - // Method m = subject.getClass().getMethod(method.getName(), - // method.getParameterTypes()); - // return m.invoke(subject, args); - } catch (Exception ex) - { - throw new PersistenceBrokerException("Error invoking method " + method.getName(), ex); - } - } - - /** - * Returns the proxies real subject. The subject will be materialized if - * necessary. - * - * @return The subject - */ - public Object getRealSubject() throws PersistenceBrokerException - { - if (_realSubject == null) - { - beforeMaterialization(); - _realSubject = materializeSubject(); - afterMaterialization(); - } - return _realSubject; - } - - /** - * [olegnitz] This looks stupid, but is really necessary for OTM: the - * materialization listener replaces the real subject by its clone to ensure - * transaction isolation. Is there a better way to do this? - */ - public void setRealSubject(Object object) - { - _realSubject = object; - } - - /** - * Retrieves the real subject from the underlying RDBMS. Override this - * method if the object is to be materialized in a specific way. - * - * @return The real subject of the proxy - */ - protected synchronized Object materializeSubject() throws PersistenceBrokerException - { - TemporaryBrokerWrapper tmp = getBroker(); + if ("getIndirectionHandler".equals(methodName) && args[0] != null) + { + return this; + } + + // now materialize the real object + subject = getRealSubject(); + + if("toString".equals(methodName) && subject == null) + { + return null; + } + /* + arminw: If the real subject doesn't exist, return 'null' for all + method calls. This could happen e.g. when the FK of a 1:1 reference + is the PK of main object. In this case the reference can be null but + the FK to the referenced object always exists (because it's the PK of the + main object) + TODO: Should we log a warn message to indicate abnormal Proxy object behavior? + */ + if(subject == null) + { + log.warn("Real object of this proxy object doesn't exist, all method will return 'null': " + getIdentity()); + return null; + } + else + { + return method.invoke(subject, args); + } + } + catch (Exception ex) + { + throw new PersistenceBrokerException("Error invoking method " + method.getName(), ex); + } + } + + /** + * Returns the proxies real subject. The subject will be materialized if + * necessary. + * + * @return The subject + */ + public Object getRealSubject() throws PersistenceBrokerException + { + if (_realSubject == null) + { + beforeMaterialization(); + _realSubject = materializeSubject(); + afterMaterialization(); + } + return _realSubject; + } + + /** + * [olegnitz] This looks stupid, but is really necessary for OTM: the + * materialization listener replaces the real subject by its clone to ensure + * transaction isolation. Is there a better way to do this? + */ + public void setRealSubject(Object object) + { + _realSubject = object; + } + + /** + * Retrieves the real subject from the underlying RDBMS. Override this + * method if the object is to be materialized in a specific way. + * + * @return The real subject of the proxy + */ + protected synchronized Object materializeSubject() throws PersistenceBrokerException + { + TemporaryBrokerWrapper tmp = getBroker(); try - { - Object realSubject = tmp.broker.getObjectByIdentity(_id); - if (realSubject == null) - { - LoggerFactory.getLogger(IndirectionHandler.class).warn( - "Can not materialize object for Identity " + _id + " - using PBKey " + getBrokerKey()); - } - return realSubject; - } catch (Exception ex) - { - throw new PersistenceBrokerException(ex); - } finally - { - tmp.close(); - } - } - - /** - * Determines whether the real subject already has been materialized. - * - * @return <code>true</code> if the real subject has already been loaded - */ - public boolean alreadyMaterialized() - { - return _realSubject != null; - } - - /** - * Generate a simple object that is serializable and placeholder for - * proxies. - * - */ - private Object generateSerializableProxy() - { - return new OJBSerializableProxy(getIdentity().getObjectsRealClass(), this); - } + { + Object realSubject = tmp.broker.getObjectByIdentity(_id); + if (realSubject == null) + { + LoggerFactory.getLogger(IndirectionHandler.class).warn( + "Can not materialize object for Identity " + _id + " - using PBKey " + getBrokerKey()); + } + return realSubject; + } catch (Exception ex) + { + throw new PersistenceBrokerException(ex); + } finally + { + tmp.close(); + } + } + + /** + * Determines whether the real subject already has been materialized. + * + * @return <code>true</code> if the real subject has already been loaded + */ + public boolean alreadyMaterialized() + { + return _realSubject != null; + } + + /** + * Generate a simple object that is serializable and placeholder for + * proxies. + * + */ + private Object generateSerializableProxy() + { + return new OJBSerializableProxy(getIdentity().getObjectsRealClass(), this); + } /** * Returns the metadata profile key used when creating this proxy. --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]