Author: degenaro
Date: Thu Nov  6 23:25:19 2014
New Revision: 1637267

URL: http://svn.apache.org/r1637267
Log:
UIMA-4069 Redesign of JD toward the main goal of classpath separation for 
container (system) code.

UserErrorHandler interface comprising configuration and error handling plus 
default implementation & test cases. (revised)

Added:
    
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java
   (with props)
    
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java
   (with props)
Modified:
    
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserErrorHandler.java
    
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserErrorHandler.java
    
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/test/java/org/apache/uima/ducc/user/jd/test/TestSuite.java

Added: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java
URL: 
http://svn.apache.org/viewvc/uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java?rev=1637267&view=auto
==============================================================================
--- 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java
 (added)
+++ 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java
 Thu Nov  6 23:25:19 2014
@@ -0,0 +1,25 @@
+/*
+ * 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.uima.ducc.user.jd.iface;
+
+public interface IJdUserDirective {
+       public boolean isKillJob();
+       public boolean isKillProcess();
+       public boolean isKillWorkItem();
+}

Propchange: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserDirective.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserErrorHandler.java
URL: 
http://svn.apache.org/viewvc/uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserErrorHandler.java?rev=1637267&r1=1637266&r2=1637267&view=diff
==============================================================================
--- 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserErrorHandler.java
 (original)
+++ 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/IJdUserErrorHandler.java
 Thu Nov  6 23:25:19 2014
@@ -18,28 +18,9 @@
 */
 package org.apache.uima.ducc.user.jd.iface;
 
-import java.util.Properties;
-
 import org.apache.uima.cas.CAS;
 
 public interface IJdUserErrorHandler {
-
-       public enum InitializeKey { 
-               killJobLimit,           // the number of work items to fail 
before the killing job
-               killWorkItemLimit,      // the number of times a work item can 
be retried due to failure
-               killProcessFlag         // whether or not the process of a 
failing work item should be killed
-               };
-       
-       public void initialize(Properties properties);
-       
-       public enum HandleKey { 
-               killJobFlag,            // request job kill
-               killJobReason,          // reason for job kill (text)
-               killWorkItemFlag,       // request work item kill
-               killWorkItemReason,     // reason for work item kill (text)
-               killProcessFlag,        // request process kill
-               killProcessReason,      // reason for process kill (text)
-               };
-       
-       public Properties handle(CAS cas, Exception e);
-}
+       public void initialize(String initializationData);
+       public IJdUserDirective handle(CAS cas, Exception e);
+}
\ No newline at end of file

Added: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java
URL: 
http://svn.apache.org/viewvc/uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java?rev=1637267&view=auto
==============================================================================
--- 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java
 (added)
+++ 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java
 Thu Nov  6 23:25:19 2014
@@ -0,0 +1,86 @@
+/*
+ * 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.uima.ducc.user.jd.iface;
+
+public class JdUserDirective implements IJdUserDirective {
+
+       private boolean killJob = false;
+       private boolean killProcess = false;
+       private boolean killWorkItem = true;
+       
+       public JdUserDirective() {
+       }
+       
+       public JdUserDirective(boolean killJob, boolean killProcess, boolean 
killWorkItem) {
+               setKillJob(killJob);
+               setKillProcess(killProcess);
+               setKillWorkItem(killWorkItem);
+       }
+       
+       @Override
+       public boolean isKillJob() {
+               return killJob;
+       }
+
+       private void setKillJob(boolean value) {
+               killJob = value;
+       }
+       
+       public void setKillJob() {
+               setKillJob(true);
+       }
+       
+       public void resetKillJob() {
+               setKillJob(false);
+       }
+       
+       @Override
+       public boolean isKillProcess() {
+               return killProcess;
+       }
+
+       private void setKillProcess(boolean value) {
+               killProcess = value;
+       }
+       
+       public void setKillProcess() {
+               setKillProcess(true);
+       }
+       
+       public void resetKillProcess() {
+               setKillProcess(false);
+       }
+       
+       @Override
+       public boolean isKillWorkItem() {
+               return killWorkItem;
+       }
+
+       private void setKillWorkItem(boolean value) {
+               killWorkItem = value;
+       }
+       
+       public void setKillWorkItem() {
+               setKillWorkItem(true);
+       }
+       
+       public void resetKillWorkItem() {
+               setKillWorkItem(false);
+       }
+}

Propchange: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserDirective.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserErrorHandler.java
URL: 
http://svn.apache.org/viewvc/uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserErrorHandler.java?rev=1637267&r1=1637266&r2=1637267&view=diff
==============================================================================
--- 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserErrorHandler.java
 (original)
+++ 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/main/java/org/apache/uima/ducc/user/jd/iface/JdUserErrorHandler.java
 Thu Nov  6 23:25:19 2014
@@ -18,156 +18,79 @@
 */
 package org.apache.uima.ducc.user.jd.iface;
 
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.uima.cas.CAS;
+import org.apache.uima.ducc.user.common.QuotedOptions;
 
 public class JdUserErrorHandler implements IJdUserErrorHandler {
 
-       private AtomicInteger exceptionLimitPerJob = new AtomicInteger(15);
-       private AtomicInteger exceptionLimitPerWorkItem = new AtomicInteger(0);
+       public enum Key { KillJobLimit, KillProcessLimit, KillWorkItemLimit };
        
-       private AtomicInteger exceptionCounter = new AtomicInteger();
+       private static int DefaultJobErrorLimit = 15;
        
-       private ConcurrentHashMap<String,AtomicInteger> map = new 
ConcurrentHashMap<String,AtomicInteger>();
+       private AtomicInteger jobErrorLimit = new 
AtomicInteger(DefaultJobErrorLimit);
        
-       public JdUserErrorHandler() {
-       }
+       private AtomicInteger jobErrorCount = new AtomicInteger(0);
        
-       public JdUserErrorHandler(Properties properties) {
-               initialize(properties);
+       public JdUserErrorHandler() {
        }
        
-       @Override
-       public void initialize(Properties properties) {
-               if(properties != null) {
-                       initializeLimitPerJob(properties);
-                       initializeLimitPerWorkItem(properties);
-               }
+       public JdUserErrorHandler(String initializationData) {
+               initialize(initializationData);
        }
        
-       private void initializeLimitPerJob(Properties properties) {
+       private Map<String, String> parse(String initializationData) {
+               Map<String, String> map = new HashMap<String, String>();
                try {
-                       String key = InitializeKey.killJobLimit.name();
-                       if(properties.containsKey(key)) {
-                               String value = properties.getProperty(key);
-                               int limit = Integer.parseInt(value);
-                               if(limit > 0) {
-                                       exceptionLimitPerJob = new 
AtomicInteger(limit);
+                       if(initializationData != null) {
+                               ArrayList<String> toks = 
QuotedOptions.tokenizeList(initializationData, true);
+                               if(toks != null) {
+                                       for(String tok : toks) {
+                                               String[] split = tok.split("=");
+                                               String key = 
split[0].trim().toLowerCase();
+                                               String value = split[1].trim();
+                                               map.put(key, value);
+                                       }
                                }
-                       }
+                       } 
                }
                catch(Exception e) {
                        e.printStackTrace();
                }
+               return map;
        }
        
-       private void initializeLimitPerWorkItem(Properties properties) {
-               try {
-                       String key = InitializeKey.killWorkItemLimit.name();
-                       if(properties.containsKey(key)) {
-                               String value = properties.getProperty(key);
-                               int limit = Integer.parseInt(value);
-                               if(limit > 0) {
-                                       exceptionLimitPerWorkItem = new 
AtomicInteger(limit);
-                               }
-                       }
-               }
-               catch(Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
        @Override
-       public Properties handle(CAS cas, Exception exception) {
-               Properties properties = new Properties();
-               exceptionCounter.incrementAndGet();
-               handleKillJob(properties, cas, exception);
-               handleKillWorkItem(properties, cas, exception);
-               return properties;
-       }
-       
-       private void killJob(Properties properties, String reason) {
-               String key;
-               String value;
-               key = HandleKey.killJobFlag.name();
-               value = Boolean.TRUE.toString();
-               properties.put(key, value);
-               key = HandleKey.killJobReason.name();
-               value = reason;
-               properties.put(key, value);
-       }
-       
-       private void killProcess(Properties properties, String reason) {
-               String key;
-               String value;
-               key = HandleKey.killProcessFlag.name();
-               value = Boolean.TRUE.toString();
-               properties.put(key, value);
-               key = HandleKey.killProcessReason.name();
-               value = reason;
-               properties.put(key, value);
-       }
-       
-       private void killWorkItem(Properties properties, String reason) {
-               String key;
-               String value;
-               key = HandleKey.killWorkItemFlag.name();
-               value = Boolean.TRUE.toString();
-               properties.put(key, value);
-               key = HandleKey.killWorkItemReason.name();
-               value = reason;
-               properties.put(key, value);
+       public void initialize(String initializationData) {
+               Map<String, String> map = parse(initializationData);
+               String key = Key.KillJobLimit.name().toLowerCase();
+               String value = map.get(key);
+               initKillJob(value);
        }
-       
-       private void handleKillJob(Properties properties, CAS cas, Exception 
exception) {
+
+       private void initKillJob(String value) {
                try {
-                       int counter = exceptionCounter.get();
-                       int limit = exceptionLimitPerJob.get();
-                       if(counter > limit) {
-                               String reasonKJ = "errors="+counter+" 
"+"limit="+limit;
-                               killJob(properties, reasonKJ);
-                       }
+                       int expect = DefaultJobErrorLimit;
+                       int update = Integer.parseInt(value);
+                       jobErrorLimit.compareAndSet(expect, update);
                }
                catch(Exception e) {
                        e.printStackTrace();
                }
        }
        
-       private void handleKillWorkItem(Properties properties, CAS cas, 
Exception exception) {
-               try {
-                       if(cas == null) {
-                               String reasonKJ = "cas=null";
-                               killJob(properties, reasonKJ);
-                               String reasonKP = "kill process (if possible!)";
-                               killProcess(properties, reasonKP);
-                       }
-                       else if(exception == null){
-                               String reasonKJ = "exception=null";
-                               killJob(properties, reasonKJ);
-                               String reasonKP = "kill process (if possible!)";
-                               killProcess(properties, reasonKP);
-                       }
-                       else {
-                               String mapKey = cas.getDocumentText();
-                               if(!map.containsKey(mapKey)) {
-                                       map.putIfAbsent(mapKey, new 
AtomicInteger(0));
-                               }
-                               AtomicInteger mapValue = map.get(mapKey);
-                               int counter = mapValue.incrementAndGet();
-                               int limit = exceptionLimitPerWorkItem.get();
-                               if(counter > limit) {
-                                       String reasonKW = "errors="+counter+" 
"+"limit="+limit;
-                                       killWorkItem(properties, reasonKW);
-                               }
-                               String reasonKP = "kill process (if possible!)";
-                               killProcess(properties, reasonKP);
-                       }
-               }
-               catch(Exception e) {
-                       e.printStackTrace();
+       @Override
+       public IJdUserDirective handle(CAS cas, Exception e) {
+               JdUserDirective jdUserDirective = new JdUserDirective();
+               jobErrorCount.incrementAndGet();
+               if(jobErrorCount.get() > jobErrorLimit.get()) {
+                       jdUserDirective.setKillJob();
                }
+               return jdUserDirective;
        }
+
 }

Modified: 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/test/java/org/apache/uima/ducc/user/jd/test/TestSuite.java
URL: 
http://svn.apache.org/viewvc/uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/test/java/org/apache/uima/ducc/user/jd/test/TestSuite.java?rev=1637267&r1=1637266&r2=1637267&view=diff
==============================================================================
--- 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/test/java/org/apache/uima/ducc/user/jd/test/TestSuite.java
 (original)
+++ 
uima/sandbox/uima-ducc/trunk/uima-ducc-user/src/test/java/org/apache/uima/ducc/user/jd/test/TestSuite.java
 Thu Nov  6 23:25:19 2014
@@ -23,15 +23,15 @@ import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.net.URL;
-import java.util.Properties;
 
 import org.apache.uima.cas.CAS;
 import org.apache.uima.ducc.user.jd.JdUserCollectionReader;
 import org.apache.uima.ducc.user.jd.JdUserException;
 import org.apache.uima.ducc.user.jd.JdUserMetaCas;
+import org.apache.uima.ducc.user.jd.iface.IJdUserDirective;
 import org.apache.uima.ducc.user.jd.iface.IJdUserErrorHandler;
-import org.apache.uima.ducc.user.jd.iface.IJdUserErrorHandler.HandleKey;
 import org.apache.uima.ducc.user.jd.iface.JdUserErrorHandler;
+import org.apache.uima.ducc.user.jd.iface.JdUserErrorHandler.Key;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -180,13 +180,11 @@ public class TestSuite {
                try {
                        IJdUserErrorHandler eh = new JdUserErrorHandler();
                        CAS cas = null;
-                       Exception e = null;
-                       Properties properties = eh.handle(cas, e);
-                       String key;
-                       String value;
-                       key = HandleKey.killJobFlag.name();
-                       value = properties.getProperty(key);
-                       assertTrue(value.equalsIgnoreCase("true"));
+                       Exception exception = null;
+                       IJdUserDirective directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == false);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
                }
                catch(Exception e) {
                        e.printStackTrace();
@@ -210,24 +208,75 @@ public class TestSuite {
                        assertTrue(jdUserMetaCas != null);
                        String serializedCas = jdUserMetaCas.getSerializedCas();
                        assertTrue(serializedCas != null);
+                       //
                        CAS cas = jdcr.deserialize(serializedCas);
-                       JdUserErrorHandler eh = new JdUserErrorHandler();
                        Exception exception = null;
-                       Properties properties = eh.handle(cas, exception);
-                       String key;
-                       String value;
-                       key = HandleKey.killJobFlag.name();
-                       value = properties.getProperty(key);
-                       assertTrue(value.equalsIgnoreCase("true"));
-                       key = HandleKey.killJobReason.name();
-                       value = properties.getProperty(key);
-                       debug(key+": "+value);
-                       key = HandleKey.killProcessFlag.name();
-                       value = properties.getProperty(key);
-                       assertTrue(value.equalsIgnoreCase("true"));
-                       key = HandleKey.killProcessReason.name();
-                       value = properties.getProperty(key);
-                       debug(key+": "+value);
+                       JdUserErrorHandler eh = null;
+                       IJdUserDirective directive = null;
+                       String plist = null;
+                       int limit = 0;
+                       //
+                       exception = null;
+                       eh = new JdUserErrorHandler();
+                       directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == false);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
+                       //
+                       exception = new RuntimeException();
+                       eh = new JdUserErrorHandler();
+                       directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == false);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
+                       //
+                       limit = 15;
+                       exception = new RuntimeException();
+                       eh = new JdUserErrorHandler();
+                       directive = eh.handle(cas, exception);
+                       for(int i=1; i<limit; i++) {
+                               directive = eh.handle(cas, exception);
+                               assertTrue(directive.isKillJob() == false);
+                               assertTrue(directive.isKillProcess() == false);
+                               assertTrue(directive.isKillWorkItem() == true);
+                       }
+                       directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == true);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
+                       //
+                       limit = 10;
+                       exception = new RuntimeException();
+                       plist = Key.KillJobLimit.name()+"="+limit;
+                       eh = new JdUserErrorHandler(plist);
+                       directive = eh.handle(cas, exception);
+                       for(int i=1; i<limit; i++) {
+                               directive = eh.handle(cas, exception);
+                               assertTrue(directive.isKillJob() == false);
+                               assertTrue(directive.isKillProcess() == false);
+                               assertTrue(directive.isKillWorkItem() == true);
+                       }
+                       directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == true);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
+                       //
+                       limit = 20;
+                       exception = new RuntimeException();
+                       plist = Key.KillJobLimit.name()+"="+limit;
+                       eh = new JdUserErrorHandler(plist);
+                       directive = eh.handle(cas, exception);
+                       for(int i=1; i<limit; i++) {
+                               directive = eh.handle(cas, exception);
+                               assertTrue(directive.isKillJob() == false);
+                               assertTrue(directive.isKillProcess() == false);
+                               assertTrue(directive.isKillWorkItem() == true);
+                       }
+                       directive = eh.handle(cas, exception);
+                       assertTrue(directive.isKillJob() == true);
+                       assertTrue(directive.isKillProcess() == false);
+                       assertTrue(directive.isKillWorkItem() == true);
+                       //
                        jdcr.recycle(cas);
                }
                catch(Exception e) {
@@ -239,49 +288,6 @@ public class TestSuite {
        @Test
        public void test08() {
                try {
-                       URL url = this.getClass().getResource("/CR100.xml");
-                       File file = new File(url.getFile());
-                       String crXml = file.getAbsolutePath();
-                       debug(crXml);
-                       String crCfg = null;
-                       JdUserCollectionReader jdcr = new 
JdUserCollectionReader(crXml, crCfg);
-                       int total = jdcr.getTotal();
-                       assertTrue(total == 100);
-                       JdUserMetaCas jdUserMetaCas = null;
-                       jdUserMetaCas = jdcr.getJdUserMetaCas();
-                       assertTrue(jdUserMetaCas != null);
-                       String serializedCas = jdUserMetaCas.getSerializedCas();
-                       assertTrue(serializedCas != null);
-                       CAS cas = jdcr.deserialize(serializedCas);
-                       JdUserErrorHandler eh = new JdUserErrorHandler();
-                       Exception exception = new RuntimeException();
-                       Properties properties = eh.handle(cas, exception);
-                       String key;
-                       String value;
-                       key = HandleKey.killWorkItemFlag.name();
-                       value = properties.getProperty(key);
-                       assertTrue(value.equalsIgnoreCase("true"));
-                       key = HandleKey.killWorkItemReason.name();
-                       value = properties.getProperty(key);
-                       debug(key+": "+value);
-                       key = HandleKey.killProcessFlag.name();
-                       value = properties.getProperty(key);
-                       assertTrue(value.equalsIgnoreCase("true"));
-                       key = HandleKey.killProcessReason.name();
-                       value = properties.getProperty(key);
-                       debug(key+": "+value);
-                       
assertTrue(!properties.containsKey(HandleKey.killJobFlag));
-                       jdcr.recycle(cas);
-               }
-               catch(Exception e) {
-                       e.printStackTrace();
-                       fail("Exception");
-               }
-       }
-       
-       @Test
-       public void test09() {
-               try {
                        int seqNo = 1;
                        String serializedCas = "ABC";
                        String documentText = "123";


Reply via email to