camel-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From davscl...@apache.org
Subject [1/3] camel git commit: [CAMEL-8059] Add CamelContext creation hook
Date Tue, 18 Nov 2014 14:43:17 GMT
Repository: camel
Updated Branches:
  refs/heads/master 8f0320dc8 -> c03a34f6f


[CAMEL-8059] Add CamelContext creation hook


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/69a9f60b
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/69a9f60b
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/69a9f60b

Branch: refs/heads/master
Commit: 69a9f60b77418499007bed2c34190dd012300672
Parents: 8f0320d
Author: Thomas Diesler <thomas.diesler@jboss.com>
Authored: Tue Nov 18 12:57:09 2014 +0100
Committer: Thomas Diesler <thomas.diesler@jboss.com>
Committed: Tue Nov 18 12:57:09 2014 +0100

----------------------------------------------------------------------
 .../camel/impl/CamelContextTrackerRegistry.java |  61 +++++++++
 .../apache/camel/impl/DefaultCamelContext.java  |  20 +--
 .../camel/impl/DefaultCamelContextRegistry.java | 136 -------------------
 .../apache/camel/spi/CamelContextRegistry.java  | 104 --------------
 .../apache/camel/spi/CamelContextTracker.java   |  41 ++++++
 .../camel/impl/CamelContextRegistryTest.java    |  76 -----------
 .../camel/impl/CamelContextTrackerTest.java     |  72 ++++++++++
 7 files changed, 180 insertions(+), 330 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/main/java/org/apache/camel/impl/CamelContextTrackerRegistry.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/CamelContextTrackerRegistry.java
b/camel-core/src/main/java/org/apache/camel/impl/CamelContextTrackerRegistry.java
new file mode 100644
index 0000000..3ec2252
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/impl/CamelContextTrackerRegistry.java
@@ -0,0 +1,61 @@
+/**
+ * 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.camel.impl;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.CamelContextTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ */
+public final class CamelContextTrackerRegistry {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CamelContextTrackerRegistry.class);
+
+    /**
+     * The registry singleton
+     */
+    public static final CamelContextTrackerRegistry INSTANCE = new CamelContextTrackerRegistry();
+    
+    private final Set<CamelContextTracker> trackers = new HashSet<CamelContextTracker>();
+
+    // Hide ctor
+    private CamelContextTrackerRegistry() {
+    }
+    
+    public synchronized void addTracker(CamelContextTracker tracker) {
+        trackers.add(tracker);
+    }
+
+    public synchronized void removeTracker(CamelContextTracker tracker) {
+        trackers.remove(tracker);
+    }
+
+    synchronized void contextCreated(CamelContext camelContext) {
+        for (CamelContextTracker tracker : trackers) {
+            try {
+            	tracker.contextCreated(camelContext);
+            } catch (Exception ex) {
+                LOG.warn("Error calling context tracker. This exception is ignored.", ex);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
index 139f59b..5d646da 100644
--- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
+++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.impl;
 
+import static org.apache.camel.util.StringQuoteHelper.doubleQuote;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
@@ -23,7 +25,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -38,6 +39,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import javax.naming.Context;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.Unmarshaller;
@@ -94,7 +96,6 @@ import org.apache.camel.processor.interceptor.HandleFault;
 import org.apache.camel.processor.interceptor.StreamCaching;
 import org.apache.camel.processor.interceptor.Tracer;
 import org.apache.camel.spi.CamelContextNameStrategy;
-import org.apache.camel.spi.CamelContextRegistry;
 import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.ComponentResolver;
 import org.apache.camel.spi.Container;
@@ -149,8 +150,6 @@ import org.apache.camel.util.URISupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.camel.util.StringQuoteHelper.doubleQuote;
-
 /**
  * Represents the context used to configure routes and the policies to use.
  *
@@ -269,9 +268,9 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon
         this.managementStrategy = createManagementStrategy();
         this.managementMBeanAssembler = createManagementMBeanAssembler();
 
-        // Register this context with the registry
-        // Note, this may register a partially constructed object
-        ((DefaultCamelContextRegistry) CamelContextRegistry.INSTANCE).afterCreate(this);
+        // Call all registered trackers with this context
+        // Note, this may use a partially constructed object
+        CamelContextTrackerRegistry.INSTANCE.contextCreated(this);
 
         // [TODO] Remove in 3.0
         Container.Instance.manage(this);
@@ -2003,10 +2002,6 @@ public class DefaultCamelContext extends ServiceSupport implements
ModelCamelCon
             setApplicationContextClassLoader(cl);
         }
 
-        // We register the context again just before start. This ensures that is is registered
on restart
-        // Listeners should only see one call to Listener.contextAdded(CamelContext)
-        ((DefaultCamelContextRegistry) CamelContextRegistry.INSTANCE).beforeStart(this);
-
         if (log.isDebugEnabled()) {
             log.debug("Using ClassResolver={}, PackageScanClassResolver={}, ApplicationContextClassLoader={}",
                     new Object[]{getClassResolver(), getPackageScanClassResolver(), getApplicationContextClassLoader()});
@@ -2259,9 +2254,6 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon
 
         // [TODO] Remove in 3.0
         Container.Instance.unmanage(this);
-
-        // Unregister this context from the registry
-        ((DefaultCamelContextRegistry) CamelContextRegistry.INSTANCE).afterStop(this);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContextRegistry.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContextRegistry.java
b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContextRegistry.java
deleted file mode 100644
index 6d65441..0000000
--- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContextRegistry.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * 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.camel.impl;
-
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import org.apache.camel.CamelContext;
-import org.apache.camel.spi.CamelContextRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The default {@link CamelContextRegistry}.
- * 
- * This implementation gives package protected access to add/remove camel contexts
- * to the camel runtime. Clients are not expected to manage the set of registered contexts.
- * 
- * Registered listeners are owned by the client which registered the listener.
- * Neither the camel runtime nor non-owning clients can control the set of registered listeners.

- */
-public final class DefaultCamelContextRegistry implements CamelContextRegistry {
-
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultCamelContextRegistry.class);
-
-    private final Set<CamelContext> contexts = new LinkedHashSet<CamelContext>();
-    private final Set<Listener> listeners = new LinkedHashSet<Listener>();
-
-    synchronized void afterCreate(CamelContext camelContext) {
-        registerContext(camelContext);
-    }
-
-    synchronized void beforeStart(CamelContext camelContext) {
-        if (!contexts.contains(camelContext)) {
-            registerContext(camelContext);
-        }
-    }
-
-    synchronized void afterStop(CamelContext camelContext) {
-        unregisterContext(camelContext);
-    }
-
-    private void registerContext(CamelContext camelContext) {
-        contexts.add(camelContext);
-        for (Listener listener : listeners) {
-            try {
-                listener.contextAdded(camelContext);
-            } catch (Throwable e) {
-                LOG.warn("Error calling registry listener. This exception is ignored.", e);
-            }
-        }
-    }
-
-    private void unregisterContext(CamelContext camelContext) {
-        contexts.remove(camelContext);
-        for (Listener listener : listeners) {
-            try {
-                listener.contextRemoved(camelContext);
-            } catch (Throwable e) {
-                LOG.warn("Error calling registry listener. This exception is ignored.", e);
-            }
-        }
-    }
-
-    @Override
-    public synchronized void addListener(Listener listener, boolean withCallback) {
-        if (withCallback) {
-            for (CamelContext ctx : contexts) {
-                listener.contextAdded(ctx);
-            }
-        }
-        listeners.add(listener);
-    }
-
-    @Override
-    public synchronized void removeListener(Listener listener, boolean withCallback) {
-        listeners.add(listener);
-        if (withCallback) {
-            for (CamelContext ctx : contexts) {
-                listener.contextAdded(ctx);
-            }
-        }
-    }
-
-    @Override
-    public synchronized Set<CamelContext> getContexts() {
-        return new LinkedHashSet<CamelContext>(contexts);
-    }
-
-    @Override
-    public synchronized Set<CamelContext> getContexts(String name) {
-        Set<CamelContext> result = new LinkedHashSet<CamelContext>();
-        for (CamelContext ctx : contexts) {
-            if (ctx.getName().equals(name)) {
-                result.add(ctx);
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public synchronized CamelContext getRequiredContext(String name) {
-        Iterator<CamelContext> it = getContexts(name).iterator();
-        if (!it.hasNext()) {
-            throw new IllegalStateException("Cannot find CamelContext with name: " + name);
-        }
-        return it.next();
-    }
-
-    @Override
-    public synchronized CamelContext getContext(String name) {
-        Iterator<CamelContext> it = getContexts(name).iterator();
-        return it.hasNext() ? it.next() : null;
-    }
-
-    @Override
-    public synchronized void clear() {
-        contexts.clear();
-        listeners.clear();
-    }
-}

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/main/java/org/apache/camel/spi/CamelContextRegistry.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/spi/CamelContextRegistry.java b/camel-core/src/main/java/org/apache/camel/spi/CamelContextRegistry.java
deleted file mode 100644
index 164fa91..0000000
--- a/camel-core/src/main/java/org/apache/camel/spi/CamelContextRegistry.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * 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.camel.spi;
-
-import java.util.Set;
-
-import org.apache.camel.CamelContext;
-import org.apache.camel.impl.DefaultCamelContext;
-import org.apache.camel.impl.DefaultCamelContextRegistry;
-
-/**
- * A global registry for camel contexts.
- * <p/>
- * The runtime registers all contexts that derive from {@link DefaultCamelContext} automatically.
- */
-public interface CamelContextRegistry {
-
-    /**
-     * The registry singleton
-     */
-    CamelContextRegistry INSTANCE = new DefaultCamelContextRegistry();
-
-    /**
-     * A listener that can be registered with he registry
-     */
-    public class Listener {
-
-        /**
-         * Called when a context is added to the registry
-         */
-        public void contextAdded(CamelContext camelContext) {
-        }
-
-        /**
-         * Called when a context is removed from the registry
-         */
-        public void contextRemoved(CamelContext camelContext) {
-        }
-    }
-
-    /**
-     * Add the given listener to the registry
-     *
-     * @param withCallback If true, the given listener is called with the set of already
registered contexts
-     */
-    void addListener(Listener listener, boolean withCallback);
-
-    /**
-     * Remove the given listener from the registry
-     *
-     * @param withCallback If true, the given listener is called with the set of already
registered contexts
-     */
-    void removeListener(Listener listener, boolean withCallback);
-
-    /**
-     * Get the set of registered contexts
-     */
-    Set<CamelContext> getContexts();
-
-    /**
-     * Get the set of registered contexts for the given name.
-     * <p/>
-     * Because the camel context name property is neither unique nor immutable
-     * the returned set may vary for the same name.
-     */
-    Set<CamelContext> getContexts(String name);
-
-    /**
-     * Get the registered context for the given name.
-     *
-     * @return The first context in the set
-     * @throws IllegalStateException when there is no registered context for the given name
-     * @see CamelContextRegistry#getContexts(String)
-     */
-    CamelContext getRequiredContext(String name);
-
-    /**
-     * Get the registered context for the given name.
-     *
-     * @return The first context in the set or null
-     * @see CamelContextRegistry#getContexts(String)
-     */
-    CamelContext getContext(String name);
-
-    /**
-     * Needed for testing purposes.
-     */
-    void clear();
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/main/java/org/apache/camel/spi/CamelContextTracker.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/spi/CamelContextTracker.java b/camel-core/src/main/java/org/apache/camel/spi/CamelContextTracker.java
new file mode 100644
index 0000000..6cce932
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/spi/CamelContextTracker.java
@@ -0,0 +1,41 @@
+/**
+ * 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.camel.spi;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.CamelContextTrackerRegistry;
+
+/**
+ * A camel context creation tracker.
+ */
+public class CamelContextTracker {
+
+    /**
+     * Called when a context is created.
+     */
+    public void contextCreated(CamelContext camelContext) {
+    	// do nothing
+    }
+
+    public final void open() {
+    	CamelContextTrackerRegistry.INSTANCE.addTracker(this);
+    }
+    
+    public final void close() {
+    	CamelContextTrackerRegistry.INSTANCE.removeTracker(this);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/test/java/org/apache/camel/impl/CamelContextRegistryTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/impl/CamelContextRegistryTest.java
b/camel-core/src/test/java/org/apache/camel/impl/CamelContextRegistryTest.java
deleted file mode 100644
index 52df959..0000000
--- a/camel-core/src/test/java/org/apache/camel/impl/CamelContextRegistryTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * 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.camel.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.TestCase;
-import org.apache.camel.CamelContext;
-import org.apache.camel.spi.CamelContextRegistry;
-
-public class CamelContextRegistryTest extends TestCase {
-
-    private final class MyListener extends CamelContextRegistry.Listener {
-
-        private List<String> names = new ArrayList<String>();
-
-        @Override
-        public void contextAdded(CamelContext camelContext) {
-            names.add(camelContext.getName());
-        }
-
-        @Override
-        public void contextRemoved(CamelContext camelContext) {
-            names.remove(camelContext.getName());
-        }
-    }
-
-    public void testContainerSet() throws Exception {
-
-        // must clear for testing purpose
-        CamelContextRegistry.INSTANCE.clear();
-
-        MyListener listener = new MyListener();
-
-        CamelContext camel1 = new DefaultCamelContext();
-        CamelContext camel2 = new DefaultCamelContext();
-
-        assertEquals(0, listener.names.size());
-
-        try {
-            CamelContextRegistry.INSTANCE.addListener(listener, true);
-            // after we set, then we should manage the 2 pending contexts
-            assertEquals(2, listener.names.size());
-
-            CamelContext camel3 = new DefaultCamelContext();
-            assertEquals(3, listener.names.size());
-            assertEquals(camel1.getName(), listener.names.get(0));
-            assertEquals(camel2.getName(), listener.names.get(1));
-            assertEquals(camel3.getName(), listener.names.get(2));
-
-            camel1.stop();
-            camel2.stop();
-            camel3.stop();
-
-            assertEquals(0, listener.names.size());
-            
-        } finally {
-            CamelContextRegistry.INSTANCE.removeListener(listener, true);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/camel/blob/69a9f60b/camel-core/src/test/java/org/apache/camel/impl/CamelContextTrackerTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/impl/CamelContextTrackerTest.java b/camel-core/src/test/java/org/apache/camel/impl/CamelContextTrackerTest.java
new file mode 100644
index 0000000..dffa7ae
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/impl/CamelContextTrackerTest.java
@@ -0,0 +1,72 @@
+/**
+ * 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.camel.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.CamelContextTracker;
+import org.apache.camel.support.LifecycleStrategySupport;
+
+public class CamelContextTrackerTest extends TestCase {
+
+    private final class MyContextTracker extends CamelContextTracker {
+
+        private List<String> names = new ArrayList<String>();
+
+		@Override
+		public void contextCreated(CamelContext camelContext) {
+			camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
+				@Override
+				public void onContextStop(CamelContext context) {
+					names.remove(context.getName());
+				}
+			});
+			names.add(camelContext.getName());
+		}
+    }
+
+    public void testContainerSet() throws Exception {
+
+        MyContextTracker tracker = new MyContextTracker();
+
+        CamelContext camel1 = new DefaultCamelContext();
+        CamelContext camel2 = new DefaultCamelContext();
+        assertEquals(0, tracker.names.size());
+
+        try {
+            tracker.open();
+            assertEquals(0, tracker.names.size());
+
+            CamelContext camel3 = new DefaultCamelContext();
+            assertEquals(1, tracker.names.size());
+            assertEquals(camel3.getName(), tracker.names.get(0));
+
+            camel1.stop();
+            camel2.stop();
+            camel3.stop();
+
+            assertEquals(0, tracker.names.size());
+            
+        } finally {
+            tracker.close();
+        }
+    }
+}


Mime
View raw message