Author: sergeyb
Date: Sun Jul  5 19:07:12 2009
New Revision: 791301

URL: http://svn.apache.org/viewvc?rev=791301&view=rev
Log:
JAXRS : adding 2 tests for using XSLTJAXBProvider on the client side, with one 
being ignored for now 

Added:
    
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java  
 (with props)
    
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
   (with props)
    
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
   (with props)
Modified:
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
    cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
    
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java

Modified: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
 (original)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
 Sun Jul  5 19:07:12 2009
@@ -380,7 +380,8 @@
             int status = conn.getResponseCode();
             if (status < 200 || status == 204 || status > 300) {
                 Object length = 
r.getMetadata().getFirst(HttpHeaders.CONTENT_LENGTH);
-                if (length == null || Integer.parseInt(length.toString()) == 
0) {
+                if (length == null || Integer.parseInt(length.toString()) == 0
+                    || status >= 400) {
                     return cls == Response.class ? cls : null;
                 }
             }
@@ -557,7 +558,7 @@
         public void onMessage(Message m) {
             
             Message message = 
cfg.getConduitSelector().getEndpoint().getBinding().createMessage(m);
-            message.put(Message.REQUESTOR_ROLE, Boolean.FALSE);
+            message.put(Message.REQUESTOR_ROLE, Boolean.TRUE);
             message.put(Message.INBOUND_MESSAGE, Boolean.TRUE);
             PhaseInterceptorChain chain = 
setupInInterceptorChain(cfg.getConduitSelector().getEndpoint());
             message.setInterceptorChain(chain);

Modified: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
 (original)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
 Sun Jul  5 19:07:12 2009
@@ -50,6 +50,7 @@
 import org.apache.cxf.jaxrs.model.OperationResourceInfo;
 import org.apache.cxf.jaxrs.model.Parameter;
 import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.URITemplate;
 import org.apache.cxf.jaxrs.provider.ProviderFactory;
 import org.apache.cxf.jaxrs.utils.FormUtils;
 import org.apache.cxf.jaxrs.utils.InjectionUtils;
@@ -159,7 +160,7 @@
         setRequestHeaders(headers, ori, types.containsKey(ParameterType.FORM), 
             bodyIndex == -1 ? null : params[bodyIndex].getClass(), 
m.getReturnType());
         
-        return doChainedInvocation(uri, headers, ori, params, bodyIndex, 
types);
+        return doChainedInvocation(uri, headers, ori, params, bodyIndex, 
types, pathParams);
         
     }
 
@@ -364,9 +365,20 @@
     
     private Object doChainedInvocation(URI uri, MultivaluedMap<String, String> 
headers, 
                           OperationResourceInfo ori, Object[] params, int 
bodyIndex, 
-                          MultivaluedMap<ParameterType, Parameter> types) 
throws Throwable {
+                          MultivaluedMap<ParameterType, Parameter> types,
+                          List<Object> pathParams) throws Throwable {
         Message m = createMessage(ori.getHttpMethod(), headers, uri);
-
+        if (pathParams.size() != 0) { 
+            List<String> vars = ori.getURITemplate().getVariables();
+            MultivaluedMap<String, String> templatesMap =  new 
MetadataMap<String, String>(vars.size());
+            for (int i = 0; i < vars.size(); i++) {
+                if (i < pathParams.size()) {
+                    templatesMap.add(vars.get(i), 
pathParams.get(i).toString());
+                }
+            }
+            m.put(URITemplate.TEMPLATE_PARAMETERS, templatesMap);
+        }
+        
         if (bodyIndex != -1 || types.containsKey(ParameterType.FORM)) {
             m.setContent(OperationResourceInfo.class, ori);
             m.put("BODY_INDEX", bodyIndex);

Modified: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
 (original)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
 Sun Jul  5 19:07:12 2009
@@ -43,7 +43,9 @@
 import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor;
 import org.apache.cxf.interceptor.Fault;
 import org.apache.cxf.jaxrs.ext.form.Form;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
 import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.URITemplate;
 import org.apache.cxf.jaxrs.utils.HttpUtils;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.message.Message;
@@ -58,6 +60,8 @@
  */
 public class WebClient extends AbstractClient {
     
+    private MultivaluedMap<String, String> templates;
+    
     protected WebClient(String baseAddress) {
         this(URI.create(baseAddress));
     }
@@ -311,6 +315,16 @@
      * @return updated WebClient
      */
     public WebClient path(String path, Object... values) {
+        URITemplate t = new URITemplate(path);
+        List<String> vars = t.getVariables();
+        if (vars.size() > 0 && vars.size() == values.length) {
+            if (templates == null) {
+                templates = new MetadataMap<String, String>();
+            }
+            for (int i = 0; i < values.length; i++) {
+                templates.add(vars.get(i), values[i].toString());
+            }
+        }
         URI u = 
UriBuilder.fromUri(URI.create("http://tempuri";)).path(path).buildFromEncoded(values);
         return path(u.getRawPath());
     }
@@ -354,6 +368,7 @@
      * @return updated WebClient
      */
     public WebClient to(String newAddress, boolean forward) {
+        clearTemplates();
         if (forward) {
             if (!newAddress.startsWith(getBaseURI().toString())) {
                 throw new IllegalArgumentException("Base address can not be 
preserved");
@@ -371,6 +386,7 @@
      * @return updated WebClient
      */
     public WebClient back(boolean fast) {
+        clearTemplates();
         if (fast) {
             getCurrentBuilder().replacePath(getBaseURI().getPath());
         } else {
@@ -455,6 +471,7 @@
     
     @Override
     public WebClient reset() {
+        clearTemplates();
         return (WebClient)super.reset();
     }
     
@@ -472,7 +489,11 @@
             headers.putSingle(HttpHeaders.ACCEPT, 
MediaType.APPLICATION_XML_TYPE.toString());
         }
         resetResponse();
-        return doChainedInvocation(httpMethod, headers, body, responseClass);
+        try {
+            return doChainedInvocation(httpMethod, headers, body, 
responseClass);
+        } finally {
+            clearTemplates();
+        }
         
     }
 
@@ -480,7 +501,7 @@
         MultivaluedMap<String, String> headers, Object body, Class<?> 
responseClass) {
         
         Message m = createMessage(httpMethod, headers, getCurrentURI());
-        
+        m.put(URITemplate.TEMPLATE_PARAMETERS, templates);
         if (body != null) {
             MessageContentsList contents = new MessageContentsList(body);
             m.setContent(List.class, contents);
@@ -517,6 +538,13 @@
         return 
createHttpConnection(getCurrentBuilder().clone().buildFromEncoded(), 
methodName);
     }
     
+    private void clearTemplates() {
+        if (templates != null) {
+            templates.clear();
+            templates = null;
+        }
+    }
+    
     private class BodyWriter extends AbstractOutDatabindingInterceptor {
 
         public BodyWriter() {

Modified: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java
 (original)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java
 Sun Jul  5 19:07:12 2009
@@ -39,6 +39,7 @@
 import org.apache.cxf.jaxrs.utils.HttpUtils;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
 
 public class UriInfoImpl implements UriInfo {
     private static final Logger LOG = LogUtils.getL7dLogger(UriInfoImpl.class);
@@ -125,6 +126,9 @@
 
     public MultivaluedMap<String, String> getPathParameters(boolean decode) {
         MetadataMap<String, String> values = new MetadataMap<String, String>();
+        if (templateParams == null) {
+            return values;
+        }
         for (Map.Entry<String, List<String>> entry : 
templateParams.entrySet()) {
             if (entry.getKey().equals(URITemplate.FINAL_MATCH_GROUP)) {
                 continue;
@@ -185,6 +189,9 @@
 
     private String getAbsolutePathAsString() {
         String address = getBaseUri().toString();
+        if (MessageUtils.isRequestor(message)) {
+            return address;
+        }
         String path = doGetPath(true, false);
         if (path.startsWith("/") && address.endsWith("/")) {
             address = address.substring(0, address.length() - 1);

Modified: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
 (original)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
 Sun Jul  5 19:07:12 2009
@@ -89,6 +89,7 @@
 import org.apache.cxf.jaxrs.provider.ProviderFactory;
 import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
 import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
 import org.apache.cxf.transport.http.AbstractHTTPDestination;
 
 public final class JAXRSUtils {
@@ -693,6 +694,9 @@
     
     @SuppressWarnings("unchecked")
     private static UriInfo createUriInfo(Message m) {
+        if (MessageUtils.isRequestor(m)) {
+            m = m.getExchange() != null ? m.getExchange().getOutMessage() : m;
+        }
         MultivaluedMap<String, String> templateParams =
             (MultivaluedMap<String, 
String>)m.get(URITemplate.TEMPLATE_PARAMETERS);
         return new UriInfoImpl(m, templateParams);

Modified: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java 
(original)
+++ 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java 
Sun Jul  5 19:07:12 2009
@@ -244,6 +244,23 @@
     }
     
     @GET
+    @Path("/books/wrapper/{bookId}/")
+    @Produces("application/xml")
+    public BookWrapper getWrappedBook(@PathParam("bookId") Long id) throws 
BookNotFoundFault {
+        BookWrapper bw = new BookWrapper();
+        Book b = new Book("CXF in Action", 99999L);
+        bw.setBook(b);
+        return bw;
+    }
+    
+    @GET
+    @Path("/books/wrapper2/{bookId}/")
+    @Produces("application/xml")
+    public Book getWrappedBook2(@PathParam("bookId") Long id) throws 
BookNotFoundFault {
+        return new Book("CXF in Action", 99999L);
+    }
+    
+    @GET
     @Path("books/custom/{bookId:\\d\\d\\d}")
     public Book getBookCustom(@PathParam("bookId") String id) throws 
BookNotFoundFault {
         return doGetBook(id);

Added: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java?rev=791301&view=auto
==============================================================================
--- 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java 
(added)
+++ 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java 
Sun Jul  5 19:07:12 2009
@@ -0,0 +1,39 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+...@xmlrootelement(name = "wrapper", namespace = "http://books";)
+public class BookWrapper {
+    private Book book;
+    
+    public BookWrapper() {
+        
+    }
+    
+    public void setBook(Book b) {
+        this.book = b;
+    }
+    
+    public Book getBook() {
+        return book;
+    }
+}

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookWrapper.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java?rev=791301&r1=791300&r2=791301&view=diff
==============================================================================
--- 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
 (original)
+++ 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
 Sun Jul  5 19:07:12 2009
@@ -24,6 +24,7 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import javax.ws.rs.core.Response;
@@ -41,9 +42,11 @@
 import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.jaxrs.ext.xml.XMLSource;
+import org.apache.cxf.jaxrs.provider.XSLTJaxbProvider;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class JAXRSClientServerBookTest extends AbstractBusClientServerTestBase 
{
@@ -99,6 +102,34 @@
     }
     
     @Test
+    public void testWebClientUnwrapBookWithXslt() throws Exception {
+        XSLTJaxbProvider provider = new XSLTJaxbProvider();
+        
provider.setInTemplate("classpath:/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl");
+        WebClient wc = 
WebClient.create("http://localhost:9080/bookstore/books/wrapper";,
+                             Collections.singletonList(provider));
+        wc.path("{id}", 123);
+        Book book = wc.get(Book.class);
+        assertNotNull(book);
+        assertEquals(123L, book.getId());
+        
+    }
+    
+    @Test
+    @Ignore
+    // uncomment once I can figure out how to set for this test only
+    // com.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize - JAXB is a pain
+    public void testProxyUnwrapBookWithXslt() throws Exception {
+        XSLTJaxbProvider provider = new XSLTJaxbProvider();
+        
provider.setInTemplate("classpath:/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl");
+        BookStore bs = JAXRSClientFactory.create("http://localhost:9080";, 
BookStore.class,
+                             Collections.singletonList(provider));
+        Book book = bs.getWrappedBook2(123L);
+        assertNotNull(book);
+        assertEquals(123L, book.getId());
+        
+    }
+    
+    @Test
     public void testOptions() throws Exception {
         WebClient wc = 
             
WebClient.create("http://localhost:9080/bookstore/bookurl/http%3A%2F%2Ftest.com%2Frss%2F123";);

Added: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl?rev=791301&view=auto
==============================================================================
--- 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
 (added)
+++ 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
 Sun Jul  5 19:07:12 2009
@@ -0,0 +1,27 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0" 
xmlns:books="http://books";>
+
+ <xsl:param name="id" select="''"/>
+
+ <xsl:template match="@*|node()">
+   <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+   </xsl:copy>
+ </xsl:template>
+ 
+ <xsl:template match="books:wrapper">
+       <xsl:apply-templates/>
+ </xsl:template>
+ 
+ <xsl:template match="book">
+    <xsl:element name="Book">
+       <xsl:apply-templates/>
+       </xsl:element>
+ </xsl:template>
+ 
+ <xsl:template match="id">
+     <xsl:copy>
+          <xsl:value-of select="$id"/>
+       </xsl:copy>
+ </xsl:template>
+
+ </xsl:stylesheet> 

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook.xsl
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl?rev=791301&view=auto
==============================================================================
--- 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
 (added)
+++ 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
 Sun Jul  5 19:07:12 2009
@@ -0,0 +1,17 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0" 
xmlns:books="http://books";>
+
+ <xsl:param name="id" select="''"/>
+
+ <xsl:template match="@*|node()">
+   <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+   </xsl:copy>
+ </xsl:template>
+ 
+ <xsl:template match="id">
+     <xsl:element name="id">
+          <xsl:value-of select="$id"/>
+       </xsl:element>
+ </xsl:template>
+
+ </xsl:stylesheet> 

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Propchange: 
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/resources/unwrapbook2.xsl
------------------------------------------------------------------------------
    svn:mime-type = text/xml


Reply via email to