Author: jsdelfino
Date: Sat Dec  2 13:41:10 2006
New Revision: 481632

URL: http://svn.apache.org/viewvc?view=rev&rev=481632
Log:
Fixed support for command style XML/HTTP to handle multiple input parameters 
correctly. Renamed internal wrapper SDO from Body to Wrapper. Changed the 
serialization code to always send the XML declaration element including the 
encoding.

Modified:
    
incubator/tuscany/cpp/sca/runtime/extensions/rest/reference/curl/src/tuscany/sca/rest/RESTServiceWrapper.cpp
    
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/ModREST.cpp
    
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/RESTServiceProxy.cpp

Modified: 
incubator/tuscany/cpp/sca/runtime/extensions/rest/reference/curl/src/tuscany/sca/rest/RESTServiceWrapper.cpp
URL: 
http://svn.apache.org/viewvc/incubator/tuscany/cpp/sca/runtime/extensions/rest/reference/curl/src/tuscany/sca/rest/RESTServiceWrapper.cpp?view=diff&rev=481632&r1=481631&r2=481632
==============================================================================
--- 
incubator/tuscany/cpp/sca/runtime/extensions/rest/reference/curl/src/tuscany/sca/rest/RESTServiceWrapper.cpp
 (original)
+++ 
incubator/tuscany/cpp/sca/runtime/extensions/rest/reference/curl/src/tuscany/sca/rest/RESTServiceWrapper.cpp
 Sat Dec  2 13:41:10 2006
@@ -22,6 +22,7 @@
 
 #include "tuscany/sca/util/Logging.h"
 #include "tuscany/sca/util/Exceptions.h"
+#include "tuscany/sca/util/Utils.h"
 #include "RESTServiceWrapper.h"
 #include "tuscany/sca/core/Operation.h"
 #include "tuscany/sca/model/Service.h"
@@ -163,11 +164,11 @@
                 } catch (SDORuntimeException&)
                 {
                     dataFactory->addType("http://tempuri.org";, "RootType", 
false, false, false);                
-                    dataFactory->addType("http://tempuri.org";, "Body", false, 
true, false);                
+                    dataFactory->addType("http://tempuri.org";, "Wrapper", 
false, true, false);                
                     dataFactory->addPropertyToType(
                         "http://tempuri.org";, "RootType",
-                        "Body",
-                        "http://tempuri.org";, "Body",
+                        "Wrapper",
+                        "http://tempuri.org";, "Wrapper",
                         false, false, true);
                     dataFactory->addType("http://tempuri.org";, "Part", false, 
true, false);                
                     dataFactory->addPropertyToType(
@@ -323,12 +324,16 @@
                         {
                             if (responsePayload != "")
                             {
-                                // TODO This is a temp hack, clean this up
-                                // Wrap the returned document inside a part 
element 
-                                string part = 
-                                "<Part xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>"
-                                + responsePayload
-                                + "</Part>";
+                                //TODO Remove this workaround once SDO 
supports loading of open top level content
+                                // The workaround is to wrap the open content 
in a wrapper element
+                                string xmldecl;
+                                string xml;
+                                Utils::rTokeniseString("?>", responsePayload, 
xmldecl, xml);
+                                string part = "<?xml version=\"1.0\" 
encoding=\"UTF-8\"?>\n";
+                                part += "<Part xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>\n";
+                                part += xml;
+                                part +=  "\n</Part>";
+                                
                                 setReturn(xmlHelper, part, operation);
                             }
                             else
@@ -658,9 +663,14 @@
                 else
                 {
                     // Not a REST interface, XML / HTTP command style
+                    curl_slist *requestHeaders = NULL;
+                    struct curl_httppost *formpost = NULL;
+                    ostringstream spayload;
+                    string requestPayload;
+                    string url; 
                     
                     // If the request contains complex content then we'll use
-                    // a multipart form POST, otherwise we use a GET
+                    // a POST, otherwise we use a GET with a query string
                     bool complexContent = false; 
                     for (int i=0; i<operation.getNParms(); i++)
                     {
@@ -670,35 +680,77 @@
                             break;
                         }
                     }
-                    struct curl_httppost *formpost = NULL;
+
                     if (complexContent)
                     {
                        // Set the target URL
                         string uri = binding->getURI();
                         ostringstream os;
                         os << uri << "/" << opName;
-                        string url = os.str();                                 
       
+                        url = os.str();                                        
                         curl_easy_setopt(curl_handle, CURLOPT_URL, 
url.c_str());
-
-                        // Build the input form
-                        struct curl_httppost *lastptr = NULL;
-                        for (int i=0; i<operation.getNParms(); i++)
+                        
+                        // Disable the 100 continue handshake
+                        requestHeaders = curl_slist_append(requestHeaders, 
"Expect:");
+                            
+                        if (operation.getNParms() == 1)
                         {
-                            ostringstream pname;
-                            pname << "param" << (i+1);
+                            // Single parameter, send it as the body of the 
POST
+                            
+                            // Create the input payload     
+                            writeParameter(xmlHelper, spayload, 
operation.getParameter(0));
+                            requestPayload = spayload.str(); 
+                            requestChunk.memory = requestPayload.c_str();
+                            requestChunk.size = requestPayload.size();
                             
-                            ostringstream pvalue;
-                            writeParameter(xmlHelper, pvalue, 
operation.getParameter(i));
+                            curl_easy_setopt(curl_handle, 
CURLOPT_POSTFIELDSIZE, requestChunk.size);
                             
-                            curl_formadd(&formpost,
-                                &lastptr,
-                                CURLFORM_COPYNAME, pname.str().c_str(),
-                                CURLFORM_COPYCONTENTS, pvalue.str().c_str(),
-                                CURLFORM_END);
+                            // Read all data using this function
+                            curl_easy_setopt(curl_handle, 
CURLOPT_READFUNCTION, read_callback);
+         
+                            // Pass our 'chunk' struct to the callback function
+                            curl_easy_setopt(curl_handle, CURLOPT_READDATA, 
(void *)&requestChunk);
+
+                            // Set the content type
+                            requestHeaders = curl_slist_append(requestHeaders, 
"Content-Type: text/xml");
+
+                            // This will be a POST                        
+                            curl_easy_setopt(curl_handle, CURLOPT_POST, true);
+                        }
+                        else
+                        {
+    
+                            // Multiple parameters, use a form type POST
+                            struct curl_httppost *lastptr = NULL;
+                            for (int i=0; i<operation.getNParms(); i++)
+                            {
+                                ostringstream pname;
+                                pname << "param" << (i+1);
+                                
+                                const char* ctype;
+                                if (operation.getParameter(i).getType() == 
Operation::DATAOBJECT)
+                                {
+                                    ctype ="text/xml"; 
+                                }
+                                else
+                                {
+                                    ctype = "text/plain";
+                                }
+                                
+                                ostringstream pvalue;
+                                writeParameter(xmlHelper, pvalue, 
operation.getParameter(i));
+                                
+                                curl_formadd(&formpost,
+                                    &lastptr,
+                                    CURLFORM_CONTENTTYPE, ctype,
+                                    CURLFORM_COPYNAME, pname.str().c_str(),
+                                    CURLFORM_COPYCONTENTS, 
pvalue.str().c_str(),
+                                    CURLFORM_END);
+                            }
+
+                            // Set the form into the request
+                            curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, 
formpost);
                         }
-                        
-                        // Set the form into the request
-                        curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, 
formpost);
                     }
                     else
                     {
@@ -720,7 +772,7 @@
                                 os << "&";
                         }
     
-                        string url = os.str();
+                        url = os.str();
                                                                 
                         curl_easy_setopt(curl_handle, CURLOPT_URL, 
url.c_str());
                     }
@@ -731,9 +783,21 @@
                     // Pass our 'chunk' struct to the callback function
                     curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void 
*)&responseChunk);
  
+                    // Configure the headers
+                    if (requestHeaders)
+                    {
+                        curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, 
requestHeaders);
+                    }
+                                                
                     // Perform the HTTP request
                     CURLcode rc = curl_easy_perform(curl_handle);
 
+                    // Free any headers
+                    if (requestHeaders)
+                    {
+                        curl_slist_free_all(requestHeaders);
+                    }
+                        
                     // Cleanup the form
                     if (complexContent)
                     {
@@ -747,8 +811,19 @@
  
                     if (responseChunk.memory)
                     {
-                        string payload((const char*)responseChunk.memory, 
responseChunk.size);
-                        setReturn(xmlHelper, payload, operation);
+                        string responsePayload((const 
char*)responseChunk.memory, responseChunk.size);
+                        
+                        //TODO Remove this workaround once SDO supports 
loading of open top level content
+                        // The workaround is to wrap the open content in a 
wrapper element
+                        string xmldecl;
+                        string xml;
+                        Utils::rTokeniseString("?>", responsePayload, xmldecl, 
xml);
+                        string part = "<?xml version=\"1.0\" 
encoding=\"UTF-8\"?>\n";
+                        part += "<Part xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>\n";
+                        part += xml;
+                        part +=  "\n</Part>";
+                        
+                        setReturn(xmlHelper, part, operation);
                     }
                 }
                 
@@ -826,7 +901,6 @@
                     {
                         DataObjectPtr dob = *(DataObjectPtr*)parm.getValue(); 
                         XMLDocumentPtr doc = xmlHelper->createDocument(dob, 
NULL, NULL);
-                        doc->setXMLDeclaration(false);
                         xmlHelper->save(doc, os);
                         break;
                     }
@@ -843,20 +917,23 @@
                 logentry();
                 
                 //TODO Remove this workaround once SDO supports loading of 
open top level content
-                // The workaround is to wrap the open content in a wrapper 
element of type OpenDataObject
-                string body = 
-                "<Body xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>"
-                + payload
-                + "</Body>";
+                // The workaround is to wrap the open content in a wrapper 
element
+                string xmldecl;
+                string xml;
+                Utils::rTokeniseString("?>", payload, xmldecl, xml);
+                string body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+                body += "<Wrapper xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>\n";
+                body += xml;
+                body +=  "\n</Wrapper>";
                 
                 // Convert the body to an SDO DataObject
-                DataObjectPtr outputBodyDataObject = NULL;
+                DataObjectPtr outputWrapperDataObject = NULL;
                 XMLDocumentPtr theXMLDocument = xmlHelper->load(body.c_str(), 
NULL);
                 if (theXMLDocument != 0)
                 {
-                    outputBodyDataObject = theXMLDocument->getRootDataObject();
+                    outputWrapperDataObject = 
theXMLDocument->getRootDataObject();
                 }
-                if(!outputBodyDataObject)
+                if(!outputWrapperDataObject)
                 {
                     ostringstream msg;
                     msg << "Could not convert received document to SDO: " << 
body;
@@ -865,17 +942,17 @@
 
                 // Get the body part
                 DataObjectPtr outputDataObject = NULL; 
-                PropertyList bpl = 
outputBodyDataObject->getInstanceProperties();
+                PropertyList bpl = 
outputWrapperDataObject->getInstanceProperties();
                 if (bpl.size()!=0)
                 {
                     if (bpl[0].isMany())
                     {
-                        DataObjectList& parts = 
outputBodyDataObject->getList((unsigned int)0);
+                        DataObjectList& parts = 
outputWrapperDataObject->getList((unsigned int)0);
                         outputDataObject = parts[0];
                     }
                     else
                     {
-                        outputDataObject = 
outputBodyDataObject->getDataObject(bpl[0]);
+                        outputDataObject = 
outputWrapperDataObject->getDataObject(bpl[0]);
                     }
                 }
                 if (outputDataObject == NULL)

Modified: 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/ModREST.cpp
URL: 
http://svn.apache.org/viewvc/incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/ModREST.cpp?view=diff&rev=481632&r1=481631&r2=481632
==============================================================================
--- 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/ModREST.cpp
 (original)
+++ 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/ModREST.cpp
 Sat Dec  2 13:41:10 2006
@@ -515,13 +515,9 @@
                         DataObjectPtr outputDataObject = 
proxy->invoke(wsdlOperation, inputDataObject);
                         
                         // Send the output DataObject
-                        char *str;
                         if (iface!=NULL &&
                             iface->getInterfaceTypeQName() == 
RESTInterface::typeQName)
                         {
-                            // Set the content type
-                            ap_set_content_type(request, "text/xml");
-                        
                             if (outputDataObject == NULL)
                             {
                                 throwException(ServiceInvocationException, 
"Null output from REST create operation");
@@ -539,18 +535,18 @@
                                         resourceDataObject,
                                         resourceDataObject->getType().getURI(),
                                         
resourceDataObject->getType().getName());
-                                    doc->setXMLDeclaration(false);
-                                    str = xm->save(doc);
+                                    char* str = xm->save(doc);
     
                                     loginfo("Sending response: %s", str);
+                                    ap_set_content_type(request, "text/xml");
                                     ap_rputs(str, request);
                                 
-                                    return OK;
                                 }
                                 else
                                 {
                                     loginfo("REST resource not found, sending 
HTTP 404 response code");
                                     request->status = HTTP_NOT_FOUND;
+                                    
                                     return OK;
                                 }
                             }
@@ -559,30 +555,37 @@
                         {
                             // Command style, send the response wrapper element
 
-                            // Set the content type
-                            ap_set_content_type(request, "text/xml");
-                    
                             if (outputDataObject == NULL)
                             {
                                 loginfo("Sending empty response");
-                                return OK;
+                                //request->status = HTTP_NO_CONTENT;
                             }
                             else
                             {
                                 XMLHelperPtr xm = 
HelperProvider::getXMLHelper(dataFactory);
-                                XMLDocumentPtr doc = xm->createDocument(
-                                    outputDataObject,
-                                    wsdlOperation.getOutputTypeUri().c_str(), 
-                                    wsdlOperation.getOutputTypeName().c_str());
-                                doc->setXMLDeclaration(false);
-                                str = xm->save(doc);
-
-                                loginfo("Sending response: %s", str);
-                                ap_rputs(str, request);
-                       
-                               return OK;
+                                DataObjectList& l = 
outputDataObject->getList("return");
+                                if (l.size() != 0)
+                                {
+                                    DataObjectPtr resultDataObject = l[0];
+                                    XMLDocumentPtr doc = xm->createDocument(
+                                        resultDataObject,
+                                        resultDataObject->getType().getURI(),
+                                        resultDataObject->getType().getName());
+                                    char* str = xm->save(doc);
+    
+                                    loginfo("Sending response: %s", str);
+                                    ap_set_content_type(request, "text/xml");
+                                    ap_rputs(str, request);
+                                }
+                                else
+                                {
+                                    loginfo("Sending empty response");
+                                    //request->status = HTTP_NO_CONTENT;
+                                }
                             }
                         }
+                        
+                        return OK;
                     }
                     else if (request->method_number == M_POST)
                     {
@@ -731,9 +734,6 @@
                         {
                             // Pure REST, send the location of the created 
resource
 
-                            // Set the content type
-                            ap_set_content_type(request, "text/xml");
-                    
                             if (outputDataObject == NULL)
                             {
                                 throwException(ServiceInvocationException, 
"Null output from REST create operation");
@@ -762,35 +762,45 @@
                             const char* loc = ap_construct_url(request->pool, 
locuri.c_str(), request);
                             loginfo("Sending resource location: %s", loc);
                             apr_table_setn(request->headers_out, "Location", 
loc);
+                            apr_table_setn(request->headers_out, 
"Content-Location", loc);
                             request->status = HTTP_CREATED;
 
                             // Send the created resource entity back to the 
client
+                            ap_set_content_type(request, "text/xml");
                             ap_rputs(input.c_str(), request);
                             
                         }
                         else
                         {
-                            // Command style, send the response wrapper element
+                            // Command style, send the response element
     
-                            // Set the content type
-                            ap_set_content_type(request, "text/xml");
-                            
                             if (outputDataObject == NULL)
                             {
                                 loginfo("Sending empty response");
+                                //request->status = HTTP_NO_CONTENT;
                             }
                             else
                             {
                                 XMLHelperPtr xm = 
HelperProvider::getXMLHelper(dataFactory);
-                                XMLDocumentPtr doc = xm->createDocument(
-                                    outputDataObject,
-                                    wsdlOperation.getOutputTypeUri().c_str(), 
-                                    wsdlOperation.getOutputTypeName().c_str());
-                                doc->setXMLDeclaration(false);
-                                char* str = xm->save(doc);
-
-                                loginfo("Sending response: %s", str);
-                               ap_rputs(str, request);
+                                DataObjectList& l = 
outputDataObject->getList("return");
+                                if (l.size() != 0)
+                                {
+                                    DataObjectPtr resultDataObject = l[0];
+                                    XMLDocumentPtr doc = xm->createDocument(
+                                        resultDataObject,
+                                        resultDataObject->getType().getURI(),
+                                        resultDataObject->getType().getName());
+                                    char* str = xm->save(doc);
+    
+                                    loginfo("Sending response: %s", str);
+                                    ap_set_content_type(request, "text/xml");
+                                    ap_rputs(str, request);
+                                }
+                                else
+                                {
+                                    loginfo("Sending empty response");
+                                    //request->status = HTTP_NO_CONTENT;
+                                }
                             }
                         }
                        
@@ -889,10 +899,9 @@
                         
                         // Dispatch to the REST proxy
                         DataObjectPtr outputDataObject = 
proxy->invoke(wsdlOperation, inputDataObject);
-                        
-                        // Send the response back to the client
-                        ap_set_content_type(request, "text/xml");
-                        
+
+                        // Empty response                        
+                        //request->status = HTTP_NO_CONTENT;
                         return OK;
                     }
                     else if (request->method_number == M_DELETE)
@@ -963,10 +972,9 @@
                         
                         // Dispatch to the REST proxy
                         DataObjectPtr outputDataObject = 
proxy->invoke(wsdlOperation, inputDataObject);
-                        
-                        // Send the response back to the client
-                        ap_set_content_type(request, "text/xml");
-                        
+
+                        // Empty response
+                        //request->status = HTTP_NO_CONTENT;                   
     
                         return OK;
                     }
                     else
@@ -1018,7 +1026,7 @@
                     {
                         
                         // The input wrapper type is not known, create an open 
DataObject 
-                        inputDataObject = 
dataFactory->create(Type::SDOTypeNamespaceURI, "OpenDataObject");
+                        inputDataObject = 
dataFactory->create("http://tempuri.org";, "Wrapper");
                     }
                 }
                         
@@ -1092,21 +1100,25 @@
              {  
                 logentry();
                 
+                
                 //TODO Remove this workaround once SDO supports loading of 
open top level content
-                // The workaround is to wrap the open content in a wrapper 
element of type OpenDataObject
-                string body = 
-                "<Body xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>"
-                + payload
-                + "</Body>";
+                // The workaround is to wrap the open content in a wrapper 
element
+                string xmldecl;
+                string xml;
+                Utils::rTokeniseString("?>", payload, xmldecl, xml);
+                string body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+                body += "<Wrapper xmlns=\"http://tempuri.org\"; 
xmlns:tns=\"http://tempuri.org\"; 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>\n";
+                body += xml;
+                body +=  "\n</Wrapper>";
                 
                 // Convert the body to an SDO DataObject
-                DataObjectPtr inputBodyDataObject = NULL;
+                DataObjectPtr inputWrapperDataObject = NULL;
                 XMLDocumentPtr theXMLDocument = xmlHelper->load(body.c_str(), 
NULL);
                 if (theXMLDocument != 0)
                 {
-                    inputBodyDataObject = theXMLDocument->getRootDataObject();
+                    inputWrapperDataObject = 
theXMLDocument->getRootDataObject();
                 }
-                if(!inputBodyDataObject)
+                if(!inputWrapperDataObject)
                 {
                     ostringstream msg;
                     msg << "Could not convert received document to SDO: " << 
body;                     
@@ -1115,17 +1127,17 @@
 
                 // Get the body part
                 DataObjectPtr inputDataObject = NULL; 
-                PropertyList bpl = 
inputBodyDataObject->getInstanceProperties();
+                PropertyList bpl = 
inputWrapperDataObject->getInstanceProperties();
                 if (bpl.size()!=0)
                 {
                     if (bpl[0].isMany())
                     {
-                        DataObjectList& parts = 
inputBodyDataObject->getList((unsigned int)0);
+                        DataObjectList& parts = 
inputWrapperDataObject->getList((unsigned int)0);
                         inputDataObject = parts[0];
                     }
                     else
                     {
-                        inputDataObject = 
inputBodyDataObject->getDataObject(bpl[0]);
+                        inputDataObject = 
inputWrapperDataObject->getDataObject(bpl[0]);
                     }
                 }
                 if (inputDataObject == NULL)

Modified: 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/RESTServiceProxy.cpp
URL: 
http://svn.apache.org/viewvc/incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/RESTServiceProxy.cpp?view=diff&rev=481632&r1=481631&r2=481632
==============================================================================
--- 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/RESTServiceProxy.cpp
 (original)
+++ 
incubator/tuscany/cpp/sca/runtime/extensions/rest/service/httpd/src/tuscany/sca/rest/RESTServiceProxy.cpp
 Sat Dec  2 13:41:10 2006
@@ -70,11 +70,11 @@
                 } catch (SDORuntimeException&)
                 {
                     dataFactory->addType("http://tempuri.org";, "RootType", 
false, false, false);                
-                    dataFactory->addType("http://tempuri.org";, "Body", false, 
true, false);                
+                    dataFactory->addType("http://tempuri.org";, "Wrapper", 
false, true, false);                
                     dataFactory->addPropertyToType(
                         "http://tempuri.org";, "RootType",
-                        "Body",
-                        "http://tempuri.org";, "Body",
+                        "Wrapper",
+                        "http://tempuri.org";, "Wrapper",
                         false, false, true);
                     dataFactory->addType("http://tempuri.org";, "Part", false, 
true, false);                
                     dataFactory->addPropertyToType(
@@ -340,7 +340,7 @@
                         catch (SDORuntimeException&)
                         {
                             // The output wrapper type is not known, create an 
open DataObject 
-                            outputDataObject = 
dataFactoryPtr->create(Type::SDOTypeNamespaceURI, "OpenDataObject");
+                            outputDataObject = 
dataFactoryPtr->create("http://tempuri.org";, "Wrapper");
                         }
                     }
                     



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to