svn commit: r1831867 [1/2] - in /ofbiz/ofbiz-framework/trunk: ./ applications/content/ applications/content/config/ applications/content/servicedef/ applications/content/src/main/java/org/apache/ofbiz/content/ftp/ applications/datamodel/data/seed/ appl...

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1831867 [1/2] - in /ofbiz/ofbiz-framework/trunk: ./ applications/content/ applications/content/config/ applications/content/servicedef/ applications/content/src/main/java/org/apache/ofbiz/content/ftp/ applications/datamodel/data/seed/ appl...

pgil
Author: pgil
Date: Fri May 18 15:53:35 2018
New Revision: 1831867

URL: http://svn.apache.org/viewvc?rev=1831867&view=rev
Log:
Implemented : File transfer management with communicationEvent and new contactMech FTP_ADDRESS
(OFBIZ-10245)

This commit introduce a new way to manage file transfer in OFBiz.
Inspired by mailing communication event management, a new communicationEventTypeId is created ('FILE_TRANSFER_COMM').

Such commEvent with classics : partyIdFrom/partyIdTo/contactMechIdTo/entryDate etc. are analysed by a job (like sendEmailDated), to send associated contents to a configured FTP/SFTP/FTPS server. If failure happens, it is catched and stored in communicationEvent, waiting for a new try.

For this purpose :

A new contactMechTypeId is introduced (FtpAddress), with its corresponding table. This contactMech store needed information for basic user/password authentication (server url and protocol, port, username, password, etc.)
A new service sendFileTransferDated that will look for pending file transfer and call following service.
A new service sendCommEventAsFtp that take a selected commEvent, check its structure and manage its status after trying the associated content file transfers.
A new service sendContentToFtp, that take a content and transfer it to a given FtpAddress.
A seca createCommEventFromFtpTransfer on sendContentToFtp to manage plural content file transfer, creating children communicationEvent to follow each content transfer separately (only for several content transfers).
A new Interface FtpClientInterface, with the 3 implementations of FTP, FTPS (To Implement), SFTP clients to manage Ftp connection and transfer.
A new property file to enable and manage redirection for testing purpose.

With this implementation, creating a communicationEvent, with a FtpAddress contactMechIdTo, and sendFileTransferDated job planned, the file is transfered to the ftp : communication event status set to COM_COMPLETE, if error occured the communication event status is set to COM_BOUNCED with error message on communicationEvent note.

Thanks Rishi for your feedbacks

Added:
    ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml   (with props)
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java   (with props)
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java   (with props)
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java   (with props)
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java   (with props)
    ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java   (with props)
    ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy   (with props)
    ofbiz/ofbiz-framework/trunk/framework/common/config/ftp.properties   (with props)
Modified:
    ofbiz/ofbiz-framework/trunk/applications/content/config/ContentErrorUiLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/content/ofbiz-component.xml
    ofbiz/ofbiz-framework/trunk/applications/datamodel/data/seed/PartySeedData.xml
    ofbiz/ofbiz-framework/trunk/applications/datamodel/entitydef/party-entitymodel.xml
    ofbiz/ofbiz-framework/trunk/applications/party/config/PartyEntityLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/party/config/PartyErrorUiLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/party/config/PartyUiLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/party/servicedef/secas.xml
    ofbiz/ofbiz-framework/trunk/applications/party/servicedef/services.xml
    ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/communication/CommunicationEventServices.java
    ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/contact/ContactMechWorker.java
    ofbiz/ofbiz-framework/trunk/applications/party/template/party/EditContactMech.ftl
    ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Contact.ftl
    ofbiz/ofbiz-framework/trunk/applications/party/webapp/partymgr/WEB-INF/controller.xml
    ofbiz/ofbiz-framework/trunk/build.gradle

Modified: ofbiz/ofbiz-framework/trunk/applications/content/config/ContentErrorUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/config/ContentErrorUiLabels.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/config/ContentErrorUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/content/config/ContentErrorUiLabels.xml Fri May 18 15:53:35 2018
@@ -303,6 +303,10 @@
         <value xml:lang="zh">没有上传文件</value>
         <value xml:lang="zh-TW">沒有上傳檔案</value>
     </property>
+    <property key="ftpservices.contact_mech_must_be_ftp">
+        <value xml:lang="en">ERROR: Contact mech must be an ftp server</value>
+        <value xml:lang="fr">ERREUR: La coordonnée destinataire doit être un serveur ftp</value>
+    </property>
     <property key="layoutEvents.content_empty">
         <value xml:lang="da">indhold er tomt</value>
         <value xml:lang="de">Inhalt ist leer</value>

Modified: ofbiz/ofbiz-framework/trunk/applications/content/ofbiz-component.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/ofbiz-component.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/ofbiz-component.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/content/ofbiz-component.xml Fri May 18 15:53:35 2018
@@ -37,6 +37,7 @@ under the License.
     <service-resource type="model" loader="main" location="servicedef/services_contenttypes.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_data.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_document.xml"/>
+    <service-resource type="model" loader="main" location="servicedef/services_ftp.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_output.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_survey.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_commevent.xml"/>

Added: ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml Fri May 18 15:53:35 2018
@@ -0,0 +1,35 @@
+<!--
+  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.
+  -->
+
+<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/services.xsd">
+    <description>Content Component Ftp Services</description>
+    <vendor>OFBiz</vendor>
+
+    <!-- DataResource services -->
+    <service name="sendContentToFtp" engine="java"
+            location="org.apache.ofbiz.content.ftp.FtpServices" invoke="sendContentToFtp" auth="true">
+        <description>Send content to FtpAddress</description>
+        <attribute name="contentId" type="String" mode="IN"/>
+        <attribute name="contactMechId" type="String" mode="IN"/>
+        <!--  used for parsing and ECAs -->
+        <attribute name="partyId" type="String" mode="IN" optional="true"/>
+        <attribute name="communicationEventId" type="String" mode="INOUT" optional="true"/>
+    </service>
+</services>

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/servicedef/services_ftp.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java Fri May 18 15:53:35 2018
@@ -0,0 +1,43 @@
+package org.apache.ofbiz.content.ftp;
+
+import org.apache.ofbiz.base.util.GeneralException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+public interface FtpClientInterface {
+
+    /**
+     * Initialization of a file transfer client, and connection to the given server
+     *
+     * @param hostname hostname to connect to
+     * @param username username to login with
+     * @param password password to login with
+     * @param port     port to connect to the server, optional
+     * @param timeout  timeout for connection process, optional
+     * @throws IOException
+     */
+    void connect(String hostname, String username, String password, Long port, Long timeout) throws IOException, GeneralException;
+
+    /**
+     * Copy of the give file to the connected server into the path.
+     *
+     * @param path     path to copy the file to
+     * @param fileName name of the copied file
+     * @param file     data to copy
+     * @throws IOException
+     */
+    void copy(String path, String fileName, InputStream file) throws IOException;
+
+    List<String> list(String path) throws IOException;
+
+    void setBinaryTransfer(boolean isBinary) throws IOException;
+
+    void setPassiveMode(boolean isPassive) throws IOException;
+
+    /**
+     * Close opened connection
+     */
+    void closeConnection() throws IOException;
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpClientInterface.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java Fri May 18 15:53:35 2018
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * 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.ofbiz.content.ftp;
+
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.FileUtil;
+import org.apache.ofbiz.base.util.GeneralException;
+import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.content.data.DataResourceWorker;
+import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.util.EntityQuery;
+import org.apache.ofbiz.entity.util.EntityUtilProperties;
+import org.apache.ofbiz.service.DispatchContext;
+import org.apache.ofbiz.service.ServiceUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * FtpServices class
+ * This class provide Ftp transfer services for content
+ */
+public class FtpServices {
+
+    public static final String module = FtpServices.class.getName();
+    public static final String resource = "ContentUiLabels";
+
+    private static FtpClientInterface createFtpClient(String serverType)
+            throws GeneralException {
+        FtpClientInterface ftpClient = null;
+        switch (serverType) {
+            case "ftp":
+                ftpClient = new SimpleFtpClient();
+                break;
+            case "ftps":
+                //TODO : to implements
+                throw new GeneralException("Ftp secured transfer protocol not yet implemented");
+            case "sftp":
+                ftpClient = new SshFtpClient();
+                break;
+        }
+        return ftpClient;
+    }
+
+    public static Map<String, Object> sendContentToFtp(DispatchContext dctx, Map<String, Object> context) {
+        Delegator delegator = dctx.getDelegator();
+        Locale locale = (Locale) context.get("locale");
+        String contactMechId = (String) context.get("contactMechId");
+        String contentId = (String) context.get("contentId");
+        String communicationEventId = (String) context.get("communicationEventId");
+        boolean forceTransferControlSuccess = EntityUtilProperties.propertyValueEqualsIgnoreCase("ftp", "ftp.force.transfer.control", "Y", delegator);
+        boolean ftpNotificationEnabled = EntityUtilProperties.propertyValueEqualsIgnoreCase("ftp", "ftp.notifications.enabled", "Y", delegator);
+
+        if (ftpNotificationEnabled) return ServiceUtil.returnSuccess();
+
+        // for ECA communicationEvent process
+        Map<String, Object> resultMap = ServiceUtil.returnSuccess();
+        resultMap.put("communicationEventId", communicationEventId);
+
+        FtpClientInterface ftpClient = null;
+
+        try {
+            //Retrieve and check contactMechType
+            GenericValue contactMech = EntityQuery.use(delegator).from("ContactMech").where("contactMechId", contactMechId).cache().queryOne();
+            GenericValue ftpAddress = EntityQuery.use(delegator).from("FtpAddress").where("contactMechId", contactMechId).cache().queryOne();
+            if (null == contactMech || null == ftpAddress || !"FTP_ADDRESS".equals(contactMech.getString("contactMechTypeId"))) {
+                String errMsg = UtilProperties.getMessage("ContentErrorUiLabels", "ftpservices.contact_mech_must_be_ftp", locale);
+                return ServiceUtil.returnError(errMsg + " " + contactMechId);
+            }
+
+            //Validate content
+            GenericValue content = EntityQuery.use(delegator).from("Content").where("contentId", contentId).cache().queryOne();
+            if (null == content) {
+                return ServiceUtil.returnError(UtilProperties.getMessage(resource, "ContentNoContentFound", UtilMisc.toMap("contentId", contentId), locale));
+            }
+
+            //ftp redirection
+            if ("Y".equalsIgnoreCase(UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.enabled"))) {
+                ftpAddress = delegator.makeValue("FtpAddress");
+                ftpAddress.put("defaultTimeout", UtilProperties.getPropertyAsLong("ftp", "ftp.notifications.redirectTo.defaultTimeout", 30000));
+                ftpAddress.put("hostname", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.hostname"));
+                ftpAddress.put("path", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.path"));
+                ftpAddress.put("port", UtilProperties.getPropertyAsLong("ftp", "ftp.notifications.redirectTo.port", 65535));
+                ftpAddress.put("username", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.username"));
+                ftpAddress.put("password", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.password"));
+                ftpAddress.put("binaryTransfer", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.binaryTransfer"));
+                ftpAddress.put("passiveMode", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.passiveMode"));
+                ftpAddress.put("zipFile", UtilProperties.getPropertyValue("ftp", "ftp.notifications.redirectTo.zipFile"));
+            }
+
+            String hostname = ftpAddress.getString("hostname");
+            if (UtilValidate.isEmpty(hostname))
+                return ServiceUtil.returnError("Ftp destination server is null");
+            else if (hostname.indexOf("://") == -1) {
+                return ServiceUtil.returnError("No protocol defined in ftp destination address");
+            }
+
+            String serverType = hostname.split("://")[0];
+            hostname = hostname.split("://")[1];
+
+            ftpClient = createFtpClient(serverType);
+            if (null == ftpClient) {
+                return ServiceUtil.returnError("Server type : " + serverType + ", not supported for hostname " + hostname);
+            }
+
+            Long defaultTimeout = ftpAddress.getLong("defaultTimeout");
+            Long port = ftpAddress.getLong("port");
+            String username = ftpAddress.getString("username");
+            String password = ftpAddress.getString("password");
+
+            if (Debug.infoOn())
+                Debug.logInfo("connecting to: " + username + "@" + ftpAddress.getString("hostname") + ":" + port, module);
+            ftpClient.connect(hostname, username, password, port, defaultTimeout);
+            boolean binary = "Y".equalsIgnoreCase(ftpAddress.getString("binaryTransfer"));
+            ftpClient.setBinaryTransfer(binary);
+            boolean passive = "Y".equalsIgnoreCase(ftpAddress.getString("passiveMode"));
+            ftpClient.setPassiveMode(passive);
+
+            GenericValue dataResource = delegator.findOne("DataResource", true, "dataResourceId", content.getString("dataResourceId"));
+            Map<String, Object> resultStream = DataResourceWorker.getDataResourceStream(dataResource, null, null, locale, null, true);
+            InputStream contentStream = (InputStream) resultStream.get("stream");
+            if (contentStream == null) {
+                return ServiceUtil.returnError("DataResource " + content.getString("dataResourceId") + " return an empty stream");
+            }
+
+            String path = ftpAddress.getString("path");
+            if (Debug.infoOn())
+                Debug.logInfo("storing local file remotely as: " + (UtilValidate.isNotEmpty(path) ? path + "/" : "") + content.getString("contentName"), module);
+
+            String fileName = content.getString("contentName");
+            String remoteFileName = fileName;
+            boolean zipFile = "Y".equalsIgnoreCase(ftpAddress.getString("zipFile"));
+            if (zipFile) {
+                //Create zip file from content input stream
+                ByteArrayInputStream zipStream = FileUtil.zipFileStream(contentStream, fileName);
+                remoteFileName = fileName + (fileName.endsWith("zip")?"":".zip");
+                ftpClient.copy(path, remoteFileName, zipStream);
+
+                zipStream.close();
+            } else {
+                ftpClient.copy(path, remoteFileName, contentStream);
+            }
+            contentStream.close();
+
+            //test if the file is correctly sent
+            if (forceTransferControlSuccess) {
+                if (Debug.infoOn()) Debug.logInfo(" Control if service really success the transfer", module);
+
+                //recreate the connection
+                ftpClient.closeConnection();
+                ftpClient = createFtpClient(serverType);
+                ftpClient.connect(hostname, username, password, port, defaultTimeout);
+                ftpClient.setBinaryTransfer(binary);
+                ftpClient.setPassiveMode(passive);
+
+                //check the file name previously copy
+                List<String> fileNames = ftpClient.list(path);
+                if (Debug.infoOn()) Debug.logInfo(" For the path " + path + " we found " + fileNames, module);
+
+                if (fileNames == null || !fileNames.contains(remoteFileName)) {
+                    return ServiceUtil.returnError("DataResource " + content.getString("dataResourceId") + " return an empty stream");
+                }
+                if (Debug.infoOn())
+                    Debug.logInfo(" Ok the file " + content.getString("contentName") + " is present", module);
+            }
+        } catch (GeneralException | IOException e) {
+            return ServiceUtil.returnError(e.getMessage());
+        } finally {
+            try {
+                if (ftpClient != null) {
+                    ftpClient.closeConnection();
+                }
+            } catch (Exception e) {
+                Debug.logWarning(e, "[getFile] Problem with FTP disconnect: ", module);
+            }
+        }
+        return resultMap;
+    }
+
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/FtpServices.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java Fri May 18 15:53:35 2018
@@ -0,0 +1,43 @@
+package org.apache.ofbiz.content.ftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+public class SecureFtpClient implements FtpClientInterface {
+
+    public static final String module = SecureFtpClient.class.getName();
+
+    /**
+     * TODO : to implements
+     */
+    @Override
+    public void connect(String hostname, String username, String password, Long port, Long timeout) throws IOException {
+
+    }
+
+    @Override
+    public void copy(String path, String fileName, InputStream file) throws IOException {
+
+    }
+
+    @Override
+    public List<String> list(String path) throws IOException {
+        return null;
+    }
+
+    @Override
+    public void setBinaryTransfer(boolean isBinary) throws IOException {
+
+    }
+
+    @Override
+    public void setPassiveMode(boolean isPassive) throws IOException {
+
+    }
+
+    @Override
+    public void closeConnection() {
+
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SecureFtpClient.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java Fri May 18 15:53:35 2018
@@ -0,0 +1,85 @@
+package org.apache.ofbiz.content.ftp;
+
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.GeneralException;
+import org.apache.ofbiz.base.util.UtilProperties;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class SimpleFtpClient implements FtpClientInterface {
+
+    public static final String module = SimpleFtpClient.class.getName();
+    private FTPClient client;
+
+    public SimpleFtpClient() {
+        client = new FTPClient();
+    }
+
+    @Override
+    public void connect(String hostname, String username, String password, Long port, Long timeout) throws IOException, GeneralException {
+        if (client == null) return;
+        if (client.isConnected()) return;
+        if (port != null) {
+            client.connect(hostname, port.intValue());
+        } else {
+            client.connect(hostname);
+        }
+        if (timeout != null) client.setDefaultTimeout(timeout.intValue());
+
+        if (!FTPReply.isPositiveCompletion(client.getReplyCode())) {
+            Debug.logError("Server refused connection", module);
+            throw new GeneralException(UtilProperties.getMessage("CommonUiLabels", "CommonFtpConnectionRefused", Locale.getDefault()));
+        }
+        if (!client.login(username, password)) {
+            Debug.logError("login failed", module);
+            throw new GeneralException(UtilProperties.getMessage("CommonUiLabels", "CommonFtpLoginFailure", Locale.getDefault()));
+        }
+    }
+
+    @Override
+    public List<String> list(String path) throws IOException {
+        FTPFile[] files = client.listFiles(path);
+        List<String> fileNames = new ArrayList<>();
+        for (FTPFile file : files) {
+            fileNames.add(file.getName());
+        }
+        return fileNames;
+    }
+
+    public void setBinaryTransfer(boolean isBinary) throws IOException {
+        if (isBinary) {
+            client.setFileType(FTP.BINARY_FILE_TYPE);
+        } else {
+            client.setFileType(FTP.ASCII_FILE_TYPE);
+        }
+    }
+
+    public void setPassiveMode(boolean isPassive) {
+        if (isPassive) {
+            client.enterLocalPassiveMode();
+        } else {
+            client.enterLocalActiveMode();
+        }
+    }
+
+    @Override
+    public void copy(String path, String fileName, InputStream file) throws IOException {
+        client.changeWorkingDirectory(path);
+        client.storeFile(fileName, file);
+    }
+
+    @Override
+    public void closeConnection() throws IOException {
+        if (client != null && client.isConnected()) {
+            client.logout();
+        }
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SimpleFtpClient.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java (added)
+++ ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java Fri May 18 15:53:35 2018
@@ -0,0 +1,74 @@
+package org.apache.ofbiz.content.ftp;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.ofbiz.base.util.UtilValidate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Basic client to copy files to an ssh ftp server
+ */
+public class SshFtpClient implements FtpClientInterface {
+
+    public static final String module = SshFtpClient.class.getName();
+
+    private SshClient client;
+    private SftpClient sftp;
+
+    public SshFtpClient() {
+        client = SshClient.setUpDefaultClient();
+        client.start();
+    }
+
+    @Override
+    public void connect(String hostname, String username, String password, Long port, Long timeout) throws IOException {
+        if (port == null) port = 22l;
+        if (timeout == null) timeout = 10000l;
+
+        if (sftp != null) return;
+        ClientSession session = client.connect(username, hostname, port.intValue()).verify(timeout.intValue()).getSession();
+        session.addPasswordIdentity(password);
+        session.auth().verify(timeout.intValue());
+        sftp = session.createSftpClient();
+    }
+
+    @Override
+    public void copy(String path, String fileName, InputStream file) throws IOException {
+        OutputStream os = sftp.write((UtilValidate.isNotEmpty(path) ? path + "/" : "") + fileName);
+        IOUtils.copy(file, os);
+        os.close();
+    }
+
+    @Override
+    public List<String> list(String path) throws IOException {
+        SftpClient.CloseableHandle handle = sftp.openDir((UtilValidate.isNotEmpty(path) ? path + "/" : ""));
+        List<String> fileNames = new ArrayList<>();
+        for (SftpClient.DirEntry dirEntry : sftp.listDir(handle)) {
+            fileNames.add(dirEntry.getFilename());
+        }
+        return fileNames;
+    }
+
+    @Override
+    public void setBinaryTransfer(boolean isBinary) throws IOException {
+    }
+
+    @Override
+    public void setPassiveMode(boolean isPassive) throws IOException {
+    }
+
+    @Override
+    public void closeConnection() {
+        if (sftp != null) {
+            client.stop();
+            sftp = null;
+        }
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/content/src/main/java/org/apache/ofbiz/content/ftp/SshFtpClient.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/applications/datamodel/data/seed/PartySeedData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/datamodel/data/seed/PartySeedData.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/datamodel/data/seed/PartySeedData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/datamodel/data/seed/PartySeedData.xml Fri May 18 15:53:35 2018
@@ -63,6 +63,7 @@ under the License.
     <ContactMechType contactMechTypeId="DOMAIN_NAME" description="Internet Domain Name" hasTable="N" parentTypeId="ELECTRONIC_ADDRESS"/>
     <ContactMechType contactMechTypeId="WEB_ADDRESS" description="Web URL/Address" hasTable="N" parentTypeId="ELECTRONIC_ADDRESS"/>
     <ContactMechType contactMechTypeId="INTERNAL_PARTYID" description="Internal Note via partyId" hasTable="N" parentTypeId="ELECTRONIC_ADDRESS"/>
+    <ContactMechType contactMechTypeId="FTP_ADDRESS" description="Ftp server connection" hasTable="Y" parentTypeId="ELECTRONIC_ADDRESS"/>
 
     <ContactMechTypePurpose contactMechPurposeTypeId="BILLING_EMAIL" contactMechTypeId="EMAIL_ADDRESS"/>
     <ContactMechTypePurpose contactMechPurposeTypeId="MARKETING_EMAIL" contactMechTypeId="EMAIL_ADDRESS"/>
@@ -117,7 +118,8 @@ under the License.
     <CommunicationEventType communicationEventTypeId="WEB_SITE_COMMUNICATI" description="Web Site" hasTable="N" contactMechTypeId="WEB_ADDRESS"/>
     <CommunicationEventType communicationEventTypeId="COMMENT_NOTE" description="Comment/Note" hasTable="N" contactMechTypeId="INTERNAL_PARTYID"/>
     <CommunicationEventType communicationEventTypeId="AUTO_EMAIL_COMM" description="Auto Email" hasTable="N" contactMechTypeId="EMAIL_ADDRESS"/>
-    
+    <CommunicationEventType communicationEventTypeId="FILE_TRANSFER_COMM" description="File transfer" hasTable="N" parentTypeId="" contactMechTypeId="FTP_ADDRESS"/>
+
     <!-- party content types -->
     <PartyContentType description="Internal Content" partyContentTypeId="INTERNAL"/>
     <PartyContentType description="User Defined Content"  partyContentTypeId="USERDEF"/>

Modified: ofbiz/ofbiz-framework/trunk/applications/datamodel/entitydef/party-entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/datamodel/entitydef/party-entitymodel.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/datamodel/entitydef/party-entitymodel.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/datamodel/entitydef/party-entitymodel.xml Fri May 18 15:53:35 2018
@@ -1304,6 +1304,24 @@ under the License.
         <index-field name="contactNumber"/>
       </index>
     </entity>
+    <entity entity-name="FtpAddress"
+            package-name="org.ofbiz.party.contact"
+            title="Ftp server Entity">
+      <field name="contactMechId" type="id"/>
+      <field name="hostname" type="long-varchar"/>
+      <field name="port" type="numeric"/>
+      <field name="username" type="long-varchar"/>
+      <field name="password" type="long-varchar" encrypt="true"/>
+      <field name="binaryTransfer" type="indicator"/>
+      <field name="path" type="long-varchar"/>
+      <field name="zipFile" type="indicator"/>
+      <field name="passiveMode" type="indicator"/>
+      <field name="defaultTimeout" type="numeric"/>
+      <prim-key field="contactMechId"/>
+      <relation type="one" fk-name="FTP_SRV_CMECH" rel-entity-name="ContactMech">
+        <key-map field-name="contactMechId"/>
+      </relation>
+    </entity>
     <entity entity-name="ValidContactMechRole"
             package-name="org.apache.ofbiz.party.contact"
             title="Valid Contact Mechanism Role Entity">

Modified: ofbiz/ofbiz-framework/trunk/applications/party/config/PartyEntityLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/config/PartyEntityLabels.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/config/PartyEntityLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/config/PartyEntityLabels.xml Fri May 18 15:53:35 2018
@@ -1078,6 +1078,11 @@
         <value xml:lang="zh">电子邮件地址</value>
         <value xml:lang="zh-TW">電子郵件位址</value>
     </property>
+    <property key="ContactMechType.description.FTP_ADDRESS">
+        <value xml:lang="de">Fileserver</value>
+        <value xml:lang="en">File server</value>
+        <value xml:lang="fr">Serveur de fichier</value>
+    </property>
     <property key="ContactMechType.description.INTERNAL_PARTYID">
         <value xml:lang="ar">ملاحظة طرف داخلية</value>
         <value xml:lang="cs">Interní poznámka</value>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/config/PartyErrorUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/config/PartyErrorUiLabels.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/config/PartyErrorUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/config/PartyErrorUiLabels.xml Fri May 18 15:53:35 2018
@@ -52,6 +52,18 @@
         <value xml:lang="zh">错误:沟通事件不是电子邮件沟通,所以不能发邮件,沟通事件标识 </value>
         <value xml:lang="zh-TW">錯誤:通訊事件不是電子郵件通訊,所以不能發郵件,通訊事件識別 </value>
     </property>
+    <property key="commeventservices.communication_event_from_contact_mech_must_be_ftp">
+        <value xml:lang="de">FEHLER: Kommunikationsereignis muss ein "Kontaktmechanismus von" besitzen das ein ftp server als Kommunikationereignis ID ist</value>
+        <value xml:lang="en">ERROR: Communication event must have a from contact mech that is an ftp server for communication event Id</value>
+        <value xml:lang="es">Error: Los eventos de comunicación deben tener un servidor ftp asociado</value>
+        <value xml:lang="fr">ERREUR: la réf. d'évènement de communication doit avoir une coordonnée qui soit un serveur ftp pour évènement de communication</value>
+    </property>
+    <property key="commeventservices.communication_event_must_be_ftp_for_ftp">
+        <value xml:lang="de">FEHLER: Kommunikationsereignis ist keine Ftp Kommunikation und kann nicht als Ftp versendet werden für Kommunikationsereignis ID</value>
+        <value xml:lang="en">ERROR: Communication event is not an Ftp communication and cannot be transfered for communication event Id </value>
+        <value xml:lang="es">ERROR: La comunicación no es un transferencia ftp y no puede se enviado</value>
+        <value xml:lang="fr">ERREUR: l'évènement de communication n'est pas un transfert ftp et ne peut être envoyé comme réf. d'évènement de communication </value>
+    </property>
     <property key="commeventservices.communication_event_not_found_failure">
         <value xml:lang="de">FEHLER: Kommunikationsereignis nicht gefunden mit der Kommunikationsereignis ID</value>
         <value xml:lang="en">ERROR: Communication Event not found for communication event ID</value>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/config/PartyUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/config/PartyUiLabels.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/config/PartyUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/config/PartyUiLabels.xml Fri May 18 15:53:35 2018
@@ -34,6 +34,11 @@
         <value xml:lang="zh">年收入</value>
         <value xml:lang="zh-TW">年收入</value>
     </property>
+    <property key="FormFieldTitle_binaryTransfer">
+        <value xml:lang="de">Binären transfer</value>
+        <value xml:lang="en">Binary transfer</value>
+        <value xml:lang="fr">Transfert binaire</value>
+    </property>
     <property key="FormFieldTitle_birthDate">
         <value xml:lang="ar">تاريخ الميلاد</value>
         <value xml:lang="cs">Datum narození</value>
@@ -339,6 +344,11 @@
         <value xml:lang="zh">默认费率</value>
         <value xml:lang="zh-TW">預設費率</value>
     </property>
+    <property key="FormFieldTitle_defaultTimeout">
+        <value xml:lang="de">Voreinstellung timeout</value>
+        <value xml:lang="en">Default timeout</value>
+        <value xml:lang="fr">Expiration par défaut</value>
+    </property>
     <property key="FormFieldTitle_deleteEmail">
         <value xml:lang="ar">حذف البريد الالكتروني</value>
         <value xml:lang="cs">Smazat e-mail</value>
@@ -605,6 +615,11 @@
         <value xml:lang="zh">高度</value>
         <value xml:lang="zh-TW">身高</value>
     </property>
+    <property key="FormFieldTitle_hostname">
+        <value xml:lang="de">Hostname</value>
+        <value xml:lang="en">Host name</value>
+        <value xml:lang="fr">Nom d'hôte</value>
+    </property>
     <property key="FormFieldTitle_infoString">
         <value xml:lang="ar">سلسلة المعلومات</value>
         <value xml:lang="de">Info String</value>
@@ -1392,6 +1407,11 @@
         <value xml:lang="zh">会员类型标识</value>
         <value xml:lang="zh-TW">團體類型識別</value>
     </property>
+    <property key="FormFieldTitle_passiveMode">
+        <value xml:lang="de">Passiv modus</value>
+        <value xml:lang="en">Passive mode</value>
+        <value xml:lang="fr">Mode passif</value>
+    </property>
     <property key="FormFieldTitle_passportExpireDate">
         <value xml:lang="ar">تاريخ إنتهاء صلاحية جواز السفر</value>
         <value xml:lang="de">Ablaufdatum Pass</value>
@@ -1447,6 +1467,18 @@
         <value xml:lang="zh">密码提示</value>
         <value xml:lang="zh-TW">密碼提示</value>
     </property>
+    <property key="FormFieldTitle_path">
+        <value xml:lang="de">Pfad</value>
+        <value xml:lang="en">Path</value>
+        <value xml:lang="fr">Chemin</value>
+        <value xml:lang="it">Percorso</value>
+        <value xml:lang="ja">パス</value>
+        <value xml:lang="pt">Caminho</value>
+        <value xml:lang="th">Path</value>
+        <value xml:lang="vi">Đường dẫn</value>
+        <value xml:lang="zh">路径</value>
+        <value xml:lang="zh-TW">路徑</value>
+    </property>
     <property key="FormFieldTitle_percentComplete">
         <value xml:lang="ar">نسبة الإتمام</value>
         <value xml:lang="de">Prozent abgeschlossen</value>
@@ -1507,6 +1539,11 @@
         <value xml:lang="zh">个人图片</value>
         <value xml:lang="zh-TW">個人圖片</value>
     </property>
+    <property key="FormFieldTitle_port">
+        <value xml:lang="de">Port</value>
+        <value xml:lang="en">Port</value>
+        <value xml:lang="fr">Port</value>
+    </property>
     <property key="FormFieldTitle_preferredContactMechId">
         <value xml:lang="ar">دليل وسيلة الإتصال المفضلة</value>
         <value xml:lang="cs">Preferovaný způsob kontaktu</value>
@@ -1995,6 +2032,11 @@
         <value xml:lang="zh">年为雇主</value>
         <value xml:lang="zh-TW">受雇年數</value>
     </property>
+    <property key="FormFieldTitle_zipFile">
+        <value xml:lang="de">Zipdatei</value>
+        <value xml:lang="en">File compression</value>
+        <value xml:lang="fr">Compression de fichier</value>
+    </property>
     <property key="NewProspect">
         <value xml:lang="ar">تم إنشاء بروسبكت جديد بنجاح.</value>
         <value xml:lang="de">Neuer Prospect wurde erfolgreich erstellt.</value>
@@ -7379,6 +7421,11 @@
         <value xml:lang="zh">找不到家庭电话号码。</value>
         <value xml:lang="zh-TW">沒有家裡電話號碼.</value>
     </property>
+    <property key="PartyHostnameMustContainProtocol">
+        <value xml:lang="de">Der Hostname muss das Protokoll enthalten</value>
+        <value xml:lang="en">Hostname must contain the protocol</value>
+        <value xml:lang="fr">Le nom d'hôte doit contenir le protocole</value>
+    </property>
     <property key="PartyImportInvalidCsvFile">
         <value xml:lang="ar">نمط ملف الCSV غير صالح (المفتاح, القيمة, التسلسل)</value>
         <value xml:lang="de">Ungültiges CSV Format (Schlüssel, Wert, Sequenz)</value>

Added: ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy (added)
+++ ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy Fri May 18 15:53:35 2018
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.service.ModelService
+import org.apache.ofbiz.service.ServiceUtil
+
+/**
+ * Create FtpAddress contact Mech
+ */
+def createFtpAddress() {
+    Map contactMech = run service: 'createContactMech', with: [contactMechTypeId: 'FTP_ADDRESS']
+    String contactMechId = contactMech.contactMechId
+    if (contactMechId) {
+        GenericValue ftpAddress = makeValue('FtpAddress', parameters)
+        ftpAddress.contactMechId = contactMechId
+        ftpAddress.create()
+    } else return error('Error creating contactMech')
+
+    Map resultMap = success()
+    resultMap.contactMechId = contactMechId
+    return resultMap
+}
+
+/**
+ * Update FtpAddress contact Mech
+ */
+def updateFtpAddressWithHistory() {
+    Map resultMap = success()
+    resultMap.oldContactMechId = parameters.contactMechId
+    resultMap.contactMechId = parameters.contactMechId
+    Map newContactMechResult
+    if (resultMap.oldContactMechId) {
+        newValue = makeValue('FtpAddress', parameters)
+        if (newValue != from('FtpAddress').where(parameters).queryOne()) {  // if there is some modifications in FtpAddress data
+            newContactMechResult = run service: 'createFtpAddress', with: parameters
+        } else { //update only contactMech
+            Map updateContactMechMap = dispatcher.getDispatchContext().makeValidContext('updateContactMech', ModelService.IN_PARAM, parameters)
+            updateContactMechMap.contactMechTypeId = 'FTP_ADDRESS'
+            newContactMechResult = run service: 'updateContactMech', with: updateContactMechMap
+        }
+
+        if (!resultMap.oldContactMechId.equals(newContactMechResult.contactMechId)) {
+            resultMap.put('contactMechId', newContactMechResult.contactMechId)
+        }
+    }
+    return resultMap
+}
+
+/**
+ * Create FtpAddress contact Mech and link it with given partyId
+ * @return
+ */
+def createPartyFtpAddress() {
+    Map contactMech = run service: 'createFtpAddress', with: parameters
+    if (ServiceUtil.isError(contactMech)) return contactMech
+    String contactMechId = contactMech.contactMechId
+
+    Map createPartyContactMechMap = parameters
+    createPartyContactMechMap.put('contactMechId', contactMechId)
+    Map serviceResult = run service: 'createPartyContactMech', with: createPartyContactMechMap
+    if (ServiceUtil.isError(serviceResult)) return serviceResult
+
+    //TODO: manage purpose
+
+    Map resultMap = success()
+    resultMap.contactMechId = contactMechId
+    return resultMap
+}
+
+def updatePartyFtpAddress() {
+    Map updateFtpResult = run service: 'updateFtpAddressWithHistory', with: parameters
+    Map result = success()
+    result.contactMechId = parameters.contactMechId
+    if (parameters.contactMechId != updateFtpResult.contactMechId) {
+        Map updatePartyContactMechMap = dispatcher.getDispatchContext().makeValidContext('updatePartyContactMech', ModelService.IN_PARAM, parameters)
+        updatePartyContactMechMap.newContactMechId = updateFtpResult.contactMechId
+        updatePartyContactMechMap.contactMechTypeId = 'FTP_ADDRESS'
+        run service: 'updatePartyContactMech', with: updatePartyContactMechMap
+        result.contactMechId = updateFtpResult.contactMechId
+    }
+    return result
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/party/groovyScripts/party/ContactMechServices.groovy
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/applications/party/servicedef/secas.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/servicedef/secas.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/servicedef/secas.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/servicedef/secas.xml Fri May 18 15:53:35 2018
@@ -92,7 +92,16 @@ under the License.
         <condition field-name="communicationEventId" operator="is-not-empty"/>
         <action service="updateCommEventAfterEmail" mode="sync" run-as-user="system"/>
     </eca>
-    
+
+    <eca service="sendContentToFtp" event="in-validate">
+        <condition field-name="communicationEventId" operator="is-empty"/>
+        <action service="createCommEventFromFtpTransfer" mode="sync" run-as-user="system"/>
+    </eca>
+    <eca service="sendContentToFtp" event="commit">
+        <condition field-name="communicationEventId" operator="is-not-empty"/>
+        <action service="setCommEventComplete" mode="sync" run-as-user="system"/>
+    </eca>
+
     <!-- all these secas are now replaced by a sheduled job (sendEmailDated) which runs every 5 minutes -->
 
     <!-- After all the emails have been sent to a contact list, mark it as complete. -->

Modified: ofbiz/ofbiz-framework/trunk/applications/party/servicedef/services.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/servicedef/services.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/servicedef/services.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/servicedef/services.xml Fri May 18 15:53:35 2018
@@ -564,7 +564,40 @@ under the License.
         <attribute name="partyIdFrom" type="String" mode="IN" optional="false"/>
         <attribute name="partyIdTo" type="String" mode="IN" optional="false"/>
     </service>
-
+    <service name="createPartyFtpAddress" engine="groovy"
+             location="component://party/groovyScripts/party/ContactMechServices.groovy" invoke="createPartyFtpAddress" auth="true">
+        <description>Create an Ftp Address associated to a party</description>
+        <permission-service service-name="partyContactMechPermissionCheck" main-action="CREATE"/>
+        <auto-attributes entity-name="ContactMech" include="nonpk" mode="IN" optional="true"/>
+        <auto-attributes entity-name="PartyContactMech" mode="IN" optional="true"/>
+        <auto-attributes entity-name="FtpAddress" mode="IN" optional="true"/>
+        <attribute name="contactMechPurposeTypeId" type="String" mode="IN" optional="true"/>
+        <attribute name="contactMechId" type="String" mode="INOUT" optional="true"/>
+    </service>
+    <service name="updatePartyFtpAddress" engine="groovy"
+             location="component://party/groovyScripts/party/ContactMechServices.groovy" invoke="updatePartyFtpAddress" auth="true">
+        <description>Update an Ftp Address associated to a party</description>
+        <permission-service service-name="partyContactMechPermissionCheck" main-action="UPDATE"/>
+        <auto-attributes entity-name="PartyContactMech" mode="IN" optional="true"/>
+        <auto-attributes entity-name="FtpAddress" mode="IN" optional="true"/>
+        <attribute name="contactMechId" type="String" mode="INOUT"/>
+    </service>
+    <service name="createFtpAddress" default-entity-name="FtpAddress" engine="groovy" invoke="createFtpAddress"
+             location="component://party/groovyScripts/party/ContactMechServices.groovy">
+        <description>create FtpAddress</description>
+        <permission-service service-name="partyBasePermissionCheck" main-action="CREATE"/>
+        <auto-attributes mode="OUT" include="pk"/>
+        <auto-attributes mode="IN" include="nonpk" optional="true"/>
+    </service>
+    <service name="updateFtpAddressWithHistory" default-entity-name="FtpAddress" engine="groovy" invoke="updateFtpAddressWithHistory"
+        location="component://party/groovyScripts/party/ContactMechServices.groovy">
+        <description>update FtpAddress</description>
+        <permission-service service-name="partyBasePermissionCheck" main-action="UPDATE"/>
+        <auto-attributes mode="IN" include="pk"/>
+        <auto-attributes mode="IN" include="nonpk" optional="true"/>
+        <attribute name="contactMechId" type="String" mode="INOUT"/> <!-- the out paramater is the id of the new address -->
+        <attribute name="oldContactMechId" type="String" mode="OUT"/> <!-- this is the id of the old address -->
+    </service>
     <!-- contact mech attribute services -->
     <service name="createContactMechAttribute" engine="entity-auto" default-entity-name="ContactMechAttribute" invoke="create" auth="true">
         <description>create a contact mech attribute record</description>
@@ -886,6 +919,25 @@ under the License.
             be of type EMAIL_COMMUNICATION. Will look for a contactMechIdTo to send the emails</description>
         <attribute name="communicationEventId" type="String" mode="IN" optional="false"/>
     </service>
+    <!-- ftp communicationEvent services -->
+    <service name="sendCommEventAsFtp" engine="java"
+             location="org.apache.ofbiz.party.communication.CommunicationEventServices" invoke="sendCommEventAsFtp" auth="true"
+             transaction-timeout="7200">  <!-- set transaction time out for 2 hours, since this sometimes may last long during big file transfer -->
+        <description>Sends communication event associated contents to a ftp server. All parameters come from CommunicationEvent, which must
+            be of type FILE_TRANSFER_COMM. Will look for a contactMechIdTo to connect to Ftp</description>
+        <attribute name="communicationEventId" type="String" mode="IN"/>
+    </service>
+    <service name="createCommEventFromFtpTransfer" engine="java"
+             location="org.apache.ofbiz.party.communication.CommunicationEventServices" invoke="createCommEventFromFtpTransfer" auth="true">
+        <description>Creates a CommunicationEvent record based on information before
+            running a sendContentToFtp service (to be used via ECA)
+        </description>
+        <attribute name="partyId" type="String" mode="IN" optional="true"/>
+        <attribute name="contentId" type="String" mode="IN"/>
+        <attribute name="contactMechId" type="String" mode="IN"/>
+        <attribute name="communicationEventId" type="String" mode="OUT"/>
+    </service>
+
 
     <!--  email to communication event ECA services -->
     <service name="createCommEventFromEmail" engine="java"

Modified: ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/communication/CommunicationEventServices.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/communication/CommunicationEventServices.java?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/communication/CommunicationEventServices.java (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/communication/CommunicationEventServices.java Fri May 18 15:53:35 2018
@@ -270,6 +270,139 @@ public class CommunicationEventServices
         return result;
     }
 
+    /**
+     * Service to send all content associated to a FILE_TRANSFER_COMM CommunicationEvent,
+     * with contactMechIdTo as a FtpAdress contactMech
+     *
+     * @param ctx
+     * @param context
+     * @return
+     */
+    public static Map<String, Object> sendCommEventAsFtp(DispatchContext ctx, Map<String, ?> context) {
+        Delegator delegator = ctx.getDelegator();
+        LocalDispatcher dispatcher = ctx.getDispatcher();
+        Locale locale = (Locale) context.get("locale");
+        GenericValue userLogin = (GenericValue) context.get("userLogin");
+
+        String communicationEventId = (String) context.get("communicationEventId");
+        List<String> errorMessages = new ArrayList<>();
+        try {
+            GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId", communicationEventId).queryOne();
+            if (communicationEvent == null) {
+                String errMsg = UtilProperties.getMessage(resource, "commeventservices.communication_event_not_found_failure", locale);
+                return ServiceUtil.returnError(errMsg + " " + communicationEventId);
+            }
+
+            if ("COM_COMPLETE".equals(communicationEvent.getString("statusId"))) return ServiceUtil.returnSuccess();
+
+            String communicationEventType = communicationEvent.getString("communicationEventTypeId");
+            if (communicationEventType == null || !"FILE_TRANSFER_COMM".equals(communicationEventType)) {
+                String errMsg = UtilProperties.getMessage(resource, "commeventservices.communication_event_must_be_ftp_for_ftp", locale);
+                return ServiceUtil.returnError(errMsg + " " + communicationEventId);
+            }
+
+            String contactMechId = communicationEvent.getString("contactMechIdTo");
+
+            // Check contactMech type to FTP_ADDRESS
+            GenericValue contactMech = EntityQuery.use(delegator).from("ContactMech").cache().where("contactMechId",contactMechId).queryOne();
+            GenericValue ftpAddress = EntityQuery.use(delegator).from("FtpAddress").cache().where("contactMechId",contactMechId).queryOne();
+            if (null == contactMech || null == ftpAddress || !"FTP_ADDRESS".equals(contactMech.getString("contactMechTypeId"))) {
+                String errMsg = UtilProperties.getMessage(resource, "commeventservices.communication_event_to_contact_mech_must_be_ftp", locale);
+                return ServiceUtil.returnError(errMsg + " " + communicationEventId);
+            }
+
+            // Get list of children communication events, to avoid same content multi-send
+            List<GenericValue> childrenCommunicationEvent = EntityQuery.use(delegator).select("communicationEventId", "statusId")
+                    .from("CommunicationEvent").where("parentCommEventId", communicationEventId).cache().queryList();
+            List<String> childrenCommunicationEventIds = EntityUtil.getFieldListFromEntityList(childrenCommunicationEvent, "communicationEventId", true);
+            // Retrieve all contents to send
+            List<GenericValue> contents = EntityQuery.use(delegator).from("CommEventContentDataResource").where("communicationEventId", communicationEventId).cache().queryList();
+
+            if (UtilValidate.isNotEmpty(contents)) {
+                if (UtilValidate.isEmpty(communicationEvent.getTimestamp("datetimeStarted"))) {
+                    //store the startDate into the communication
+                    Map<String, Object> updateCommEventResult = dispatcher.runSync("updateCommunicationEvent",
+                            UtilMisc.toMap("communicationEventId", communicationEventId, "datetimeStarted", UtilDateTime.nowTimestamp(), "userLogin", userLogin), 600, true);
+                    if (ServiceUtil.isError(updateCommEventResult)) {
+                        errorMessages.add(ServiceUtil.getErrorMessage(updateCommEventResult));
+                    }
+                }
+
+                for (GenericValue content : contents) {
+                    Map<String, Object> ftpServiceMap = new HashMap<>();
+                    //store the child Communication Event, to keep track of errorMessages in note field
+                    String childCommunicationEventId = "";
+                    ftpServiceMap.put("userLogin", userLogin);
+                    ftpServiceMap.put("contentId", content.getString("contentId"));
+                    ftpServiceMap.put("partyId", communicationEvent.getString("partyIdTo"));
+                    ftpServiceMap.put("contactMechId", contactMechId);
+                    // no need to create a child CommEvent if it is a single content transfer
+                    if (contents.size() == 1)
+                        ftpServiceMap.put("communicationEventId", communicationEvent.get("communicationEventId"));
+                    else {
+                        // check if currentContent is already sent by an existing children communicationEvent
+                        EntityCondition sentCond = EntityCondition.makeCondition(UtilMisc.toList(
+                                EntityCondition.makeCondition("communicationEventId", EntityOperator.IN, childrenCommunicationEventIds),
+                                EntityCondition.makeCondition("contentId", content.getString("contentId"))));
+                        GenericValue alreadySent = EntityQuery.use(delegator).from("CommEventContentAssoc").where(sentCond).cache().queryFirst();
+
+                        if (null != alreadySent) {
+                            GenericValue childCommEvent = EntityUtil.getFirst(EntityUtil.filterByCondition(childrenCommunicationEvent,
+                                    EntityCondition.makeCondition("communicationEventId", alreadySent.getString("communicationEventId"))));
+                            // if completely sent, continue to next content
+                            if ("COM_COMPLETE".equals(childCommEvent.getString("statusId"))) continue;
+                            ftpServiceMap.put("communicationEventId", childCommEvent.getString("communicationEventId"));
+                        }
+                    }
+
+                    Map<String, Object> resultTmp = dispatcher.runSync("sendContentToFtp", ftpServiceMap, 600, true);
+                    if (ServiceUtil.isError(resultTmp)) {
+                        errorMessages.add(ServiceUtil.getErrorMessage(resultTmp));
+                    }
+
+                    // attach the parent communication event to the new event created when sending the content, and store error if needed
+                    if (UtilValidate.isNotEmpty(resultTmp.get("communicationEventId"))) childCommunicationEventId = (String) resultTmp.get("communicationEventId");
+                    if (UtilValidate.isNotEmpty(childCommunicationEventId) && !childCommunicationEventId.equals(communicationEventId)) {
+                        GenericValue childCommunicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId", childCommunicationEventId).queryOne();
+                        childCommunicationEvent.set("parentCommEventId", communicationEventId);
+                        if (ServiceUtil.isError(resultTmp)) {
+                            childCommunicationEvent.set("statusId", "COM_BOUNCED");
+                            childCommunicationEvent.set("note", ServiceUtil.getErrorMessage(resultTmp));
+                        }
+                        childCommunicationEvent.store();
+                    }
+                }
+            } else {
+                errorMessages.add(UtilProperties.getMessage(resource, "commeventservices.communication_event_not_without_content", locale));
+            }
+
+            if (errorMessages.size() > 0) {
+                communicationEvent.set("statusId", "COM_BOUNCED");
+                communicationEvent.set("note", errorMessages.toString());
+                communicationEvent.store();
+            } else {
+                //Update content status
+                for (GenericValue content : contents) {
+                    Map<String, Object> updateContentResult = dispatcher.runSync("setContentStatus", UtilMisc.<String, Object>toMap("contentId", content.getString("contentId"), "statusId", "CTNT_PUBLISHED", "userLogin", userLogin));
+                    if (ServiceUtil.isError(updateContentResult)) {
+                        errorMessages.add(ServiceUtil.getErrorMessage(updateContentResult));
+                    }
+                }
+
+                Map<String, Object> completeResult = dispatcher.runSync("setCommEventComplete", UtilMisc.<String, Object>toMap("communicationEventId", communicationEventId, "userLogin", userLogin));
+                if (ServiceUtil.isError(completeResult)) {
+                    errorMessages.add(ServiceUtil.getErrorMessage(completeResult));
+                }
+            }
+        } catch (GenericEntityException | GenericServiceException e) {
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        if (errorMessages.size() > 0) {
+            return ServiceUtil.returnFailure(errorMessages);
+        }
+        return ServiceUtil.returnSuccess();
+    }
+
     public static Map<String, Object> sendEmailToContactList(DispatchContext ctx, Map<String, ? extends Object> context) {
         Delegator delegator = ctx.getDelegator();
         LocalDispatcher dispatcher = ctx.getDispatcher();
@@ -514,7 +647,7 @@ public class CommunicationEventServices
         String partyIdFrom = (String) context.get("partyIdFrom");
 
         try {
-            GenericValue communicationEvent = delegator.findOne("CommunicationEvent", true, "communicationEventId", communicationEventId);
+            GenericValue communicationEvent = EntityQuery.use(delegator).from("CommunicationEvent").where("communicationEventId", communicationEventId).cache().queryOne();
             if (communicationEvent == null) {
                 return ServiceUtil.returnError(UtilProperties.getMessage("PartyUiLabels", "PartyCommunicationEventNotFound",
                         UtilMisc.toMap("communicationEventId", communicationEventId), (Locale) context.get("locale")));
@@ -536,6 +669,60 @@ public class CommunicationEventServices
     }
 
     /*
+     * Store an outgoing file transfer as a communication event;
+     * runs as a pre-invoke ECA on sendContentToFtp service
+     * - service should run as the 'system' user
+     */
+    public static Map<String, Object> createCommEventFromFtpTransfer(DispatchContext dctx, Map<String, ? extends Object> context) {
+        LocalDispatcher dispatcher = dctx.getDispatcher();
+
+        GenericValue userLogin = (GenericValue) context.get("userLogin");
+        String contentId = (String) context.get("contentId");
+        String contactMechId = (String) context.get("contactMechId");
+        String partyId = (String) context.get("partyId");
+        String communicationEventId;
+
+        Timestamp now = UtilDateTime.nowTimestamp();
+
+        Map<String, Object> commEventMap = new HashMap<>();
+        commEventMap.put("communicationEventTypeId", "FILE_TRANSFER_COMM");
+        commEventMap.put("contactMechTypeId", "FTP_ADDRESS");
+        commEventMap.put("contactMechIdTo", contactMechId);
+        commEventMap.put("statusId", "COM_PENDING");
+        commEventMap.put("datetimeStarted", now);
+        commEventMap.put("entryDate", now);
+        commEventMap.put("userLogin", userLogin);
+        if (UtilValidate.isNotEmpty(partyId)) {
+            commEventMap.put("partyIdTo", partyId);
+        }
+
+        Map<String, Object> createResult;
+        try {
+            createResult = dispatcher.runSync("createCommunicationEvent", commEventMap);
+            if (ServiceUtil.isError(createResult)) {
+                return createResult;
+            }
+            communicationEventId = (String) createResult.get("communicationEventId");
+
+            //add content to newly created commEvent
+            Map createCommEventContentMap = new HashMap<>();
+            createCommEventContentMap.put("userLogin", userLogin);
+            createCommEventContentMap.put("contentId", contentId);
+            createCommEventContentMap.put("communicationEventId", communicationEventId);
+            createResult = dispatcher.runSync("createCommEventContentAssoc", createCommEventContentMap);
+            if (ServiceUtil.isError(createResult)) {
+                return createResult;
+            }
+        } catch (GenericServiceException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError(e.getMessage());
+        }
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        result.put("communicationEventId", communicationEventId);
+        return result;
+    }
+
+    /*
      * Store an outgoing email as a communication event;
      * runs as a pre-invoke ECA on sendMail and sendMultipartMail services
      * - service should run as the 'system' user

Modified: ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/contact/ContactMechWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/contact/ContactMechWorker.java?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/contact/ContactMechWorker.java (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/src/main/java/org/apache/ofbiz/party/contact/ContactMechWorker.java Fri May 18 15:53:35 2018
@@ -123,6 +123,8 @@ public class ContactMechWorker {
                         partyContactMechValueMap.put("postalAddress", contactMech.getRelatedOne("PostalAddress", false));
                     } else if ("TELECOM_NUMBER".equals(contactMech.getString("contactMechTypeId"))) {
                         partyContactMechValueMap.put("telecomNumber", contactMech.getRelatedOne("TelecomNumber", false));
+                    } else if ("FTP_ADDRESS".equals(contactMech.getString("contactMechTypeId"))) {
+                        partyContactMechValueMap.put("ftpAddress", contactMech.getRelatedOne("FtpAddress", false));
                     }
                 } catch (GenericEntityException e) {
                     Debug.logWarning(e, module);
@@ -463,6 +465,8 @@ public class ContactMechWorker {
                 requestName = "createTelecomNumber";
             } else if ("EMAIL_ADDRESS".equals(contactMechTypeId)) {
                 requestName = "createEmailAddress";
+            } else if ("FTP_ADDRESS".equals(contactMechTypeId)) {
+                requestName = "createFtpAddress";
             } else {
                 requestName = "createContactMech";
             }
@@ -474,6 +478,8 @@ public class ContactMechWorker {
                 requestName = "updateTelecomNumber";
             } else if ("EMAIL_ADDRESS".equals(contactMechTypeId)) {
                 requestName = "updateEmailAddress";
+            } else if ("FTP_ADDRESS".equals(contactMechTypeId)) {
+                requestName = "updateFtpAddress";
             } else {
                 requestName = "updateContactMech";
             }
@@ -506,6 +512,19 @@ public class ContactMechWorker {
             if (telecomNumber != null) {
                 target.put("telecomNumber", telecomNumber);
             }
+        } else if ("FTP_ADDRESS".equals(contactMechTypeId)) {
+            GenericValue ftpAddress = null;
+
+            try {
+                if (contactMech != null) {
+                    ftpAddress = contactMech.getRelatedOne("FtpAddress", false);
+                }
+            } catch (GenericEntityException e) {
+                Debug.logWarning(e, module);
+            }
+            if (ftpAddress != null) {
+                target.put("ftpAddress", ftpAddress);
+            }
         }
 
         if ("true".equals(request.getParameter("useValues"))) {

Modified: ofbiz/ofbiz-framework/trunk/applications/party/template/party/EditContactMech.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/template/party/EditContactMech.ftl?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/template/party/EditContactMech.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/template/party/EditContactMech.ftl Fri May 18 15:53:35 2018
@@ -200,12 +200,86 @@ under the License.
       <td>[${uiLabelMap.CommonCountryCode}] [${uiLabelMap.PartyAreaCode}] [${uiLabelMap.PartyContactNumber}] [${uiLabelMap.PartyContactExt}]</td>
     </tr>
   <#elseif "EMAIL_ADDRESS" = mechMap.contactMechTypeId!>
-    <tr>
-      <td class="label">${mechMap.contactMechType.get("description",locale)}</td>
-      <td>
-        <input type="text" size="60" maxlength="255" name="emailAddress" value="${(mechMap.contactMech.infoString)?default(request.getParameter('emailAddress')!)}" />
-      </td>
-    </tr>
+      <tr>
+          <td class="label">${mechMap.contactMechType.get("description",locale)}</td>
+          <td>
+              <input type="text" size="60" maxlength="255" name="emailAddress" value="${(mechMap.contactMech.infoString)?default(request.getParameter('emailAddress')!)}" />
+          </td>
+      </tr>
+  <#elseif "FTP_ADDRESS" = mechMap.contactMechTypeId!>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_hostname}</td>
+          <td>
+              <input type="text" size="60" maxlength="255" name="hostname" value="${(mechMap.ftpAddress.hostname)!request.getParameter('hostname')!}" />
+              <span class="tooltip">${uiLabelMap.PartyHostnameMustContainProtocol} (ftp://, sftp://, ftps://...)</span>
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_port}</td>
+          <td>
+              <input type="text" size="6" maxlength="6" name="port" value="${(mechMap.ftpAddress.port)!request.getParameter('port')!}" />
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.CommonUsername}</td>
+          <td>
+              <input type="text" size="60" maxlength="255" name="username" value="${(mechMap.ftpAddress.username)!request.getParameter('username')!''}" />
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.CommonPassword}</td>
+          <td>
+              <input type="text" size="60" maxlength="255" name="password" value="${(mechMap.ftpAddress.password)!request.getParameter('password')!''}" />
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_binaryTransfer}</td>
+          <td>
+              <select name="binaryTransfer">
+                  <#if "Y" == (mechMap.ftpAddress.binaryTransfer)!""><option value="Y">${uiLabelMap.CommonY}</option></#if>
+                  <#if "N" == (mechMap.ftpAddress.binaryTransfer)!""><option value="N">${uiLabelMap.CommonN}</option></#if>
+                  <option></option>
+                  <option value="Y">${uiLabelMap.CommonY}</option>
+                  <option value="N">${uiLabelMap.CommonN}</option>
+              </select>
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_path}</td>
+          <td>
+              <input type="text" size="60" maxlength="255" name="path" value="${(mechMap.ftpAddress.path)!request.getParameter('path')!''}" />
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_zipFile}</td>
+          <td>
+              <select name="zipFile">
+                  <#if "Y" == (mechMap.ftpAddress.zipFile)!""><option value="Y">${uiLabelMap.CommonY}</option></#if>
+                  <#if "N" == (mechMap.ftpAddress.zipFile)!""><option value="N">${uiLabelMap.CommonN}</option></#if>
+                  <option></option>
+                  <option value="Y">${uiLabelMap.CommonY}</option>
+                  <option value="N">${uiLabelMap.CommonN}</option>
+              </select>
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_passiveMode}</td>
+          <td>
+              <select name="passiveMode">
+                  <#if "Y" == (mechMap.ftpAddress.passiveMode)!""><option value="Y">${uiLabelMap.CommonY}</option></#if>
+                  <#if "N" == (mechMap.ftpAddress.passiveMode)!""><option value="N">${uiLabelMap.CommonN}</option></#if>
+                  <option></option>
+                  <option value="Y">${uiLabelMap.CommonY}</option>
+                  <option value="N">${uiLabelMap.CommonN}</option>
+              </select>
+          </td>
+      </tr>
+      <tr>
+          <td class="label">${uiLabelMap.FormFieldTitle_defaultTimeout}</td>
+          <td>
+              <input type="text" size="10" maxlength="10" name="defaultTimeout" value="${(mechMap.ftpAddress.defaultTimeout)!request.getParameter('defaultTimeout')!''}" />
+          </td>
+      </tr>
   <#else>
     <tr>
       <td class="label">${mechMap.contactMechType.get("description",locale)}</td>

Modified: ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Contact.ftl
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Contact.ftl?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Contact.ftl (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/template/party/profileblocks/Contact.ftl Fri May 18 15:53:35 2018
@@ -95,6 +95,19 @@ under the License.
                       <input name="communicationEventTypeId" value="EMAIL_COMMUNICATION" type="hidden"/>
                     </form><a class="buttontext" href="javascript:document.createEmail${contactMech.infoString?replace('&#64;','')?replace('&#x40;','')?replace('.','')}.submit()">${uiLabelMap.CommonSendEmail}</a>
                   </div>
+                <#elseif "FTP_ADDRESS" = contactMech.contactMechTypeId>
+                    <#if contactMechMap.ftpAddress?has_content>
+                    <#assign ftpAddress = contactMechMap.ftpAddress>
+                    <div>
+                        <b><#if ftpAddress.hostname?has_content>${ftpAddress.hostname!}</#if><#if ftpAddress.port?has_content>:${ftpAddress.port!}</#if><#if ftpAddress.path?has_content>:${ftpAddress.path!}</#if></b>
+                        <br/>${uiLabelMap.CommonUsername} : ${ftpAddress.username!}
+                        <br/>${uiLabelMap.CommonPassword} : ${ftpAddress.password!}
+                        <br/>${uiLabelMap.FormFieldTitle_binaryTransfer} : ${ftpAddress.binaryTransfer!}
+                        <br/>${uiLabelMap.FormFieldTitle_zipFile} : ${ftpAddress.zipFile!}
+                        <br/>${uiLabelMap.FormFieldTitle_passiveMode} : ${ftpAddress.passiveMode!}
+                        <br/>${uiLabelMap.FormFieldTitle_defaultTimeout} : ${ftpAddress.defaultTimeout!}
+                    </div>
+                    </#if>
                 <#elseif "WEB_ADDRESS" = contactMech.contactMechTypeId>
                   <div>
                     ${contactMech.infoString!}

Modified: ofbiz/ofbiz-framework/trunk/applications/party/webapp/partymgr/WEB-INF/controller.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/party/webapp/partymgr/WEB-INF/controller.xml?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/party/webapp/partymgr/WEB-INF/controller.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/party/webapp/partymgr/WEB-INF/controller.xml Fri May 18 15:53:35 2018
@@ -191,6 +191,19 @@ under the License.
         <response name="error" type="view" value="editcontactmech"/>
     </request-map>
 
+    <request-map uri="createFtpAddress">
+        <security https="true" auth="true"/>
+        <event type="service" invoke="createPartyFtpAddress"/>
+        <response name="success" type="view" value="editcontactmech"/>
+        <response name="error" type="view" value="editcontactmech"/>
+    </request-map>
+    <request-map uri="updateFtpAddress">
+        <security https="true" auth="true"/>
+        <event type="service" invoke="updatePartyFtpAddress"/>
+        <response name="success" type="view" value="editcontactmech"/>
+        <response name="error" type="view" value="editcontactmech"/>
+    </request-map>
+
     <request-map uri="createPartyContactMechPurpose">
         <security https="true" auth="true"/>
         <event type="service" invoke="createPartyContactMechPurpose"/>

Modified: ofbiz/ofbiz-framework/trunk/build.gradle
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/build.gradle?rev=1831867&r1=1831866&r2=1831867&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/build.gradle (original)
+++ ofbiz/ofbiz-framework/trunk/build.gradle Fri May 18 15:53:35 2018
@@ -131,6 +131,7 @@ dependencies {
     compile 'org.apache.logging.log4j:log4j-api:2.10.0' // the API of log4j 2
     compile 'org.apache.poi:poi:3.17'
     compile 'org.apache.shiro:shiro-core:1.4.0'
+    compile 'org.apache.sshd:sshd-core:1.7.0'
     compile 'org.apache.tika:tika-core:1.16'
     compile 'org.apache.tika:tika-parsers:1.16'
     compile 'org.apache.tomcat:tomcat-catalina-ha:9.0.7'

Added: ofbiz/ofbiz-framework/trunk/framework/common/config/ftp.properties
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/common/config/ftp.properties?rev=1831867&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/common/config/ftp.properties (added)
+++ ofbiz/ofbiz-framework/trunk/framework/common/config/ftp.properties Fri May 18 15:53:35 2018
@@ -0,0 +1,35 @@
+###############################################################################
+# 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.
+###############################################################################
+
+# -- ftp notifications enabled (Y|N)
+ftp.notifications.enabled=N
+
+# -- redirect all ftp notifications to this server for testing
+ftp.notifications.redirectTo.enabled=Y
+ftp.notifications.redirectTo.hostname=localhost
+ftp.notifications.redirectTo.port=65535
+ftp.notifications.redirectTo.username=admin
+ftp.notifications.redirectTo.password=ofbiz
+ftp.notifications.redirectTo.binaryTransfer=Y
+ftp.notifications.redirectTo.path=
+ftp.notifications.redirectTo.passiveMode=Y
+ftp.notifications.redirectTo.defaultTimeout=
+
+# -- if you have some pb with a ftp server, set it to Y to force OFBiz to control the file copy
+ftp.force.transfer.control=N
\ No newline at end of file