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]

Reply via email to