db-ojb-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From arm...@apache.org
Subject svn commit: r492879 - /db/ojb/branches/OJB_1_0_RELEASE/src/java/org/apache/ojb/broker/core/proxy/AbstractIndirectionHandler.java
Date Fri, 05 Jan 2007 03:04:34 GMT
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 {@link 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
-	 *             {@link 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 {@link 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
+     *             {@link 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: ojb-dev-unsubscribe@db.apache.org
For additional commands, e-mail: ojb-dev-help@db.apache.org


Mime
View raw message