jackrabbit-oak-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mdue...@apache.org
Subject svn commit: r1394862 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/kernel/ main/java/org/apache/jackrabbit/oak/plugins/index/old/ main/java/org/apache/jackrabbit/oak/plugins/memory/ test/java/org/apache/jackrabbit/oak/ker...
Date Fri, 05 Oct 2012 23:47:15 GMT
Author: mduerig
Date: Fri Oct  5 23:47:15 2012
New Revision: 1394862

URL: http://svn.apache.org/viewvc?rev=1394862&view=rev
Log:
OAK-350: Unify PropertyState and CoreValue
- KernelNodeState now creates PropertyStates directly instead of looping them through CoreValues

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
  (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
  (with props)
Removed:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/CoreValueMapper.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PrefixContentIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/CoreValueFactoryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/JsopUtil.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/JsopDiff.java
Fri Oct  5 23:47:15 2012
@@ -154,15 +154,15 @@ class JsopDiff implements NodeStateDiff 
             case PropertyType.BINARY:
                 for (Blob value : property.getValue(BINARIES)) {
                     InputStream is = value.getNewStream();
-                    String binId = CoreValueMapper.getHintForType(type) + ':' + kernel.write(is);
+                    String binId = TypeCodes.getCodeForType(type) + ':' + kernel.write(is);
                     close(is);
                     jsop.value(binId);
                 }
                 break;
             default:
                 for (String value : property.getValue(STRINGS)) {
-                    if (PropertyType.STRING != type || CoreValueMapper.startsWithHint(value))
{
-                        value = CoreValueMapper.getHintForType(type) + ':' + value;
+                    if (PropertyType.STRING != type || TypeCodes.startsWithCode(value)) {
+                        value = TypeCodes.getCodeForType(type) + ':' + value;
                     }
                     jsop.value(value);
                 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java?rev=1394862&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
(added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
Fri Oct  5 23:47:15 2012
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.kernel;
+
+import java.io.InputStream;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.util.MicroKernelInputStream;
+import org.apache.jackrabbit.oak.plugins.memory.AbstractBlob;
+
+public class KernelBlob extends AbstractBlob {
+    private final String binaryID;
+    private final MicroKernel kernel;
+
+    public KernelBlob(String binaryID, MicroKernel kernel) {
+        this.binaryID = binaryID;
+        this.kernel = kernel;
+    }
+
+    @Nonnull
+    @Override
+    public InputStream getNewStream() {
+        return new MicroKernelInputStream(kernel, binaryID);
+    }
+
+    @Override
+    public long length() {
+        return kernel.getLength(binaryID);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelBlob.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java
Fri Oct  5 23:47:15 2012
@@ -36,10 +36,8 @@ import org.apache.jackrabbit.mk.api.Micr
 import org.apache.jackrabbit.mk.api.MicroKernelException;
 import org.apache.jackrabbit.mk.json.JsopReader;
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
-import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
-import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.spi.state.AbstractChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
@@ -48,8 +46,8 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.apache.jackrabbit.oak.kernel.CoreValueMapper.fromJsopReader;
-import static org.apache.jackrabbit.oak.kernel.CoreValueMapper.listFromJsopReader;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.readArrayProperty;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.readProperty;
 
 /**
  * Basic {@link NodeState} implementation based on the {@link MicroKernel}
@@ -123,11 +121,9 @@ public final class KernelNodeState exten
                     }
                     childPaths.put(name, childPath);
                 } else if (reader.matches('[')) {
-                    List<CoreValue> values = listFromJsopReader(reader, kernel);
-                    properties.put(name, PropertyStates.createProperty(name, values));
+                    properties.put(name, readArrayProperty(name, reader, kernel));
                 } else {
-                    CoreValue cv = fromJsopReader(reader, kernel);
-                    properties.put(name, PropertyStates.createProperty(name, cv));
+                    properties.put(name, readProperty(name, reader, kernel));
                 }
             } while (reader.matches(','));
             reader.read('}');

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java?rev=1394862&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
(added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
Fri Oct  5 23:47:15 2012
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.kernel;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.jcr.PropertyType;
+
+/**
+ * TypeCodes maps between {@code Type} and the code used to prefix
+ * its json serialisation.
+ */
+public class TypeCodes {
+    private static final Map<Integer, String> TYPE2CODE = new HashMap<Integer, String>();
+    private static final Map<String, Integer> CODE2TYPE = new HashMap<String, Integer>();
+
+    static {
+        for (int type = PropertyType.UNDEFINED; type <= PropertyType.DECIMAL; type++)
{
+            String code = PropertyType.nameFromValue(type).substring(0, 3).toLowerCase(Locale.ENGLISH);
+            TYPE2CODE.put(type, code);
+            CODE2TYPE.put(code, type);
+        }
+    }
+
+    private TypeCodes() { }
+
+    /**
+     * Returns {@code true} if the specified JSON String represents a value
+     * serialization that is prefixed with a type code.
+     *
+     * @param jsonString The JSON String representation of a {@code CoreValue}
+     * @return {@code true} if the {@code jsonString} starts with a type
+     * code; {@code false} otherwise.
+     */
+    public static boolean startsWithCode(String jsonString) {
+        return jsonString.length() >= 4 && jsonString.charAt(3) == ':';
+    }
+
+    /**
+     * Get the type code for the given property type.
+     *
+     * @param propertyType the property type
+     * @return the type code
+     */
+    public static String getCodeForType(int propertyType) {
+        return TYPE2CODE.get(propertyType);
+    }
+
+    /**
+     * Get the property type for the given type code.
+     * @param code  the type code
+     * @return  the property type.
+     */
+    public static int getTypeForCode(String code) {
+        return CODE2TYPE.get(code);
+    }
+
+}
\ No newline at end of file

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/TypeCodes.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PrefixContentIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PrefixContentIndex.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PrefixContentIndex.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PrefixContentIndex.java
Fri Oct  5 23:47:15 2012
@@ -23,7 +23,7 @@ import java.util.Iterator;
 import javax.jcr.PropertyType;
 
 import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.kernel.CoreValueMapper;
+import org.apache.jackrabbit.oak.kernel.TypeCodes;
 import org.apache.jackrabbit.oak.query.index.IndexRowImpl;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
@@ -64,8 +64,8 @@ public class PrefixContentIndex implemen
             if (restriction.propertyType == PropertyType.UNDEFINED) {
                 continue;
             }
-            String hint = CoreValueMapper.getHintForType(restriction.propertyType);
-            String prefix = hint + ":";
+            String code = TypeCodes.getCodeForType(restriction.propertyType);
+            String prefix = code + ":";
             if (prefix.equals(index.getPrefix())) {
                 return restriction;
             }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
Fri Oct  5 23:47:15 2012
@@ -25,10 +25,14 @@ import javax.annotation.Nonnull;
 import javax.jcr.PropertyType;
 
 import com.google.common.collect.Lists;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.json.JsopReader;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.kernel.TypeCodes;
+import org.apache.jackrabbit.oak.kernel.KernelBlob;
 
 import static org.apache.jackrabbit.oak.api.Type.DATE;
 import static org.apache.jackrabbit.oak.api.Type.DATES;
@@ -324,4 +328,70 @@ public final class PropertyStates {
         }
         return new BinariesPropertyState(name, blobs);
     }
+
+    public static PropertyState readArrayProperty(String name, JsopReader reader, MicroKernel
kernel) {
+        int type = PropertyType.STRING;
+        List<Object> values = Lists.newArrayList();
+        while (!reader.matches(']')) {
+            if (reader.matches(JsopReader.NUMBER)) {
+                String number = reader.getToken();
+                type = PropertyType.LONG;
+                values.add(StringPropertyState.getLong(number));
+            } else if (reader.matches(JsopReader.TRUE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(true);
+            } else if (reader.matches(JsopReader.FALSE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(false);
+            } else if (reader.matches(JsopReader.STRING)) {
+                String jsonString = reader.getToken();
+                if (TypeCodes.startsWithCode(jsonString)) {
+                    type = TypeCodes.getTypeForCode(jsonString.substring(0, 3));
+                    String value = jsonString.substring(4);
+                    if (type == PropertyType.BINARY) {
+                        values.add(new KernelBlob(value, kernel));
+                    } else if(type == PropertyType.DOUBLE) {
+                        values.add(StringPropertyState.getDouble(value));
+                    } else if(type == PropertyType.DECIMAL) {
+                        values.add(StringPropertyState.getDecimal(value));
+                    } else {
+                        values.add(value);
+                    }
+                } else {
+                    type = PropertyType.STRING;
+                    values.add(jsonString);
+                }
+            } else {
+                throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
+            }
+            reader.matches(',');
+        }
+        return createProperty(name, values, (Type<Object>) Type.fromTag(type, true));
+    }
+
+    public static PropertyState readProperty(String name, JsopReader reader, MicroKernel
kernel) {
+        if (reader.matches(JsopReader.NUMBER)) {
+            String number = reader.getToken();
+            return createProperty(name, number, PropertyType.LONG);
+        } else if (reader.matches(JsopReader.TRUE)) {
+            return booleanProperty(name, true);
+        } else if (reader.matches(JsopReader.FALSE)) {
+            return booleanProperty(name, false);
+        } else if (reader.matches(JsopReader.STRING)) {
+            String jsonString = reader.getToken();
+            if (TypeCodes.startsWithCode(jsonString)) {
+                int type = TypeCodes.getTypeForCode(jsonString.substring(0, 3));
+                String value = jsonString.substring(4);
+                if (type == PropertyType.BINARY) {
+                    return binaryProperty(name, new KernelBlob(value, kernel));
+                } else {
+                    return createProperty(name, value, type);
+                }
+            } else {
+                return stringProperty(name, jsonString);
+            }
+        } else {
+            throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
+        }
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/CoreValueFactoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/CoreValueFactoryTest.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/CoreValueFactoryTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/CoreValueFactoryTest.java
Fri Oct  5 23:47:15 2012
@@ -25,11 +25,11 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+
 import javax.jcr.PropertyType;
 
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.mk.core.MicroKernelImpl;
-import org.apache.jackrabbit.mk.json.JsopReader;
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.CoreValueFactory;
@@ -145,39 +145,15 @@ public class CoreValueFactoryTest {
     }
 
     @Test
-    public void testFromJsonValue() throws IOException {
-        for (CoreValue v : singleValueMap.keySet()) {
-            String json = singleValueMap.get(v);
-            JsopReader reader = new JsopTokenizer(json);
-            assertEquals(v, CoreValueMapper.fromJsopReader(reader, kernel));
-        }
-    }
-
-    @Test
-    public void testListFromJsopReader() throws IOException {
-        for (String json : mvValueMap.keySet()) {
-            List<CoreValue> values = mvValueMap.get(json);
-            JsopReader reader = new JsopTokenizer(json);
-            if (reader.matches('[')) {
-                assertEquals(values, CoreValueMapper.listFromJsopReader(reader, kernel));
-            }
-        }
-    }
-
-    @Test
-    public void testHints() {
-        HashSet<String> hints = new HashSet<String>();
+    public void testTypeCodes() {
+        HashSet<String> codes = new HashSet<String>();
         for (int i = PropertyType.UNDEFINED; i <= PropertyType.DECIMAL; i++) {
-            String hint = CoreValueMapper.getHintForType(i);
-            assertTrue(hints.add(hint));
-            assertEquals(3, hint.length());
-            assertTrue(CoreValueMapper.startsWithHint(hint + ":"));
+            String code = TypeCodes.getCodeForType(i);
+            assertTrue(codes.add(code));
+            assertEquals(3, code.length());
+            assertTrue(TypeCodes.startsWithCode(code + ":"));
             String def = getDefaultValue(i).getString();
-            JsopTokenizer t = new JsopTokenizer("\"" + hint + ":" + def + "\"");
-            CoreValue cv = CoreValueMapper.fromJsopReader(t,
-                    MemoryValueFactory.INSTANCE);
-            assertEquals(i, cv.getType());
-            assertEquals(def, cv.getString());
+            JsopTokenizer t = new JsopTokenizer("\"" + code + ":" + def + "\"");
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/JsopUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/JsopUtil.java?rev=1394862&r1=1394861&r2=1394862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/JsopUtil.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/JsopUtil.java
Fri Oct  5 23:47:15 2012
@@ -16,16 +16,11 @@
  */
 package org.apache.jackrabbit.oak.query;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
-import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.CoreValueFactory;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.kernel.CoreValueMapper;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 
 /**
@@ -101,14 +96,9 @@ public class JsopUtil {
                     tokenizer.read('}');
                 }
             } else if (tokenizer.matches('[')) {
-                List<CoreValue> mvp = new ArrayList<CoreValue>();
-                do {
-                    mvp.add(CoreValueMapper.fromJsopReader(tokenizer, vf));
-                } while (tokenizer.matches(','));
-                tokenizer.read(']');
-                t.setProperty(PropertyStates.createProperty(key, mvp));
+                t.setProperty(PropertyStates.readArrayProperty(key, tokenizer, null));
             } else {
-                t.setProperty(PropertyStates.createProperty(key, CoreValueMapper.fromJsopReader(tokenizer,
vf)));
+                t.setProperty(PropertyStates.readProperty(key, tokenizer, null));
             }
         } while (tokenizer.matches(','));
     }



Mime
View raw message