{"action":"created","comment":{"url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/comments/173801625","pull_request_review_id":103048646,"id":173801625,"diff_hunk":"@@ -0,0 +1,390 @@\n+/*\n+ * ProActive Parallel Suite(TM):\n+ * The Open Source library for parallel and distributed\n+ * Workflows & Scheduling, Orchestration, Cloud Automation\n+ * and Big Data Analysis on Enterprise Grids & Clouds.\n+ *\n+ * Copyright (c) 2007 - 2017 ActiveEon\n+ * Contact: contact@activeeon.com\n+ *\n+ * This library is free software: you can redistribute it and/or\n+ * modify it under the terms of the GNU Affero General Public License\n+ * as published by the Free Software Foundation: version 3 of\n+ * the License.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU Affero General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Affero General Public License\n+ * along with this program. If not, see <http://www.gnu.org/licenses/>.\n+ *\n+ * If needed, contact us to obtain a release under GPL Version 2 or 3\n+ * or a different license than the AGPL.\n+ */\n+package org.ow2.proactive.scheduler.examples;\n+\n+import java.io.BufferedOutputStream;\n+import java.io.File;\n+import java.io.FileInputStream;\n+import java.io.FileOutputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.io.Serializable;\n+import java.nio.file.Files;\n+import java.nio.file.Path;\n+import java.nio.file.Paths;\n+import java.util.Enumeration;\n+import java.util.Map;\n+import java.util.zip.ZipEntry;\n+import java.util.zip.ZipFile;\n+\n+import org.apache.commons.net.ftp.FTP;\n+import org.apache.commons.net.ftp.FTPClient;\n+import org.apache.commons.net.ftp.FTPFile;\n+import org.apache.commons.net.ftp.FTPReply;\n+import org.ow2.proactive.scheduler.common.task.TaskResult;\n+import org.ow2.proactive.scheduler.common.task.executable.JavaExecutable;\n+\n+\n+/**\n+ * Import and export file(s) using FTPConnector server.\n+ * This task can be launched with parameters.\n+ *\n+ * @author The ProActive Team\n+ */\n+public class FTPConnector extends JavaExecutable {\n+\n+    private static final String DEFAULT_FTP_HOSTNAME = \"localhost\";\n+\n+    private static final int DEFAULT_FTP_PORT = 21;\n+\n+    private String ftpHostname = DEFAULT_FTP_HOSTNAME;\n+\n+    private int ftpPort = DEFAULT_FTP_PORT;\n+\n+    private boolean ftpExtractArchive;\n+\n+    private String ftpLocalFilePath = null;\n+\n+    private String ftpRemoteFilePath = null;\n+\n+    private String ftpMode = null;\n+\n+    private String ftpUsername = null;\n+\n+    private String ftpPassword = null;\n+\n+    private static final String FTP_LOCAL_FILE_PATH = \"ftpLocalFilePath\";\n+\n+    /**\n+     * @see JavaExecutable#init(Map)\n+     */\n+    @Override\n+    public void init(Map<String, Serializable> args) throws Exception {\n+        if (args.containsKey(\"ftpHostname\")) {\n+            ftpHostname = args.get(\"ftpHostname\").toString();\n+        }\n+        if (args.containsKey(\"ftpPort\")) {\n+            try {\n+                ftpPort = Integer.parseInt(args.get(\"ftpPort\").toString());\n+            } catch (NumberFormatException e) {\n+            }\n+        }\n+        if (args.containsKey(\"ftpExtractArchive\")) {\n+            ftpExtractArchive = Boolean.parseBoolean(args.get(\"ftpExtractArchive\").toString());\n+        }\n+        if (args.containsKey(FTP_LOCAL_FILE_PATH)) {\n+            ftpLocalFilePath = args.get(FTP_LOCAL_FILE_PATH) == null ? \"\" : args.get(FTP_LOCAL_FILE_PATH).toString();\n+        }\n+        if (args.containsKey(\"ftpRemoteFilePath\")) {\n+            ftpRemoteFilePath = args.get(\"ftpRemoteFilePath\").toString();\n+        }\n+        if (args.containsKey(\"ftpMode\")) {\n+            ftpMode = args.get(\"ftpMode\").toString();\n+        }\n+        ftpUsername = getThirdPartyCredential(\"FTP_USERNAME\");\n+        ftpPassword = getThirdPartyCredential(\"FTP_PASSWORD\");\n+        if (ftpUsername == null || ftpPassword == null) {\n+            throw new IllegalArgumentException(\"You first need to add your ftp username and password (FTP_USERNAME, FTP_PASSWORD) to the third party credentials\");\n+        }\n+    }\n+\n+    /**\n+     * @see JavaExecutable#execute(TaskResult[])\n+     */\n+    @Override\n+    public Serializable execute(TaskResult... results) throws IOException, IllegalArgumentException {\n+\n+        FTPClient ftpClient = new FTPClient();\n+        // connect and login to the server\n+        ftpClient.connect(ftpHostname, ftpPort);\n+        int reply = ftpClient.getReplyCode();\n+        if (!FTPReply.isPositiveCompletion(reply)) {\n+            ftpClient.disconnect();\n+            throw new IOException(\"Exception in connecting to FTP Server\");\n+        }\n+        ftpClient.login(ftpUsername, ftpPassword);\n+\n+        // use local passive mode to pass firewall\n+        ftpClient.enterLocalPassiveMode();\n+\n+        getOut().println(\"Connected\");\n+\n+        //FTP mode is GET\n+        if (ftpMode.equals(\"GET\")) {\n+            FtpGet(ftpClient);\n+        }\n+        //FTP mode is PUT\n+        else if (ftpMode.equals(\"PUT\")) {\n+            FtpPut(ftpClient);\n+        }\n+        // log out and disconnect from the server\n+        ftpClient.logout();\n+        ftpClient.disconnect();\n+\n+        getOut().println(\"Disconnected\");\n+\n+        return 1;\n+    }\n+\n+    private void FtpGet(FTPClient ftpClient) throws IOException, IllegalArgumentException {\n+        getOut().println(\"Importing file(s) from \" + ftpRemoteFilePath + \" to \" + ftpLocalFilePath);\n+        FTPFile[] ftpFile = ftpClient.listFiles(ftpRemoteFilePath);\n+        if (ftpFile.length == 0)\n+            throw new IllegalArgumentException(ftpRemoteFilePath + \" not found. Please, enter a valid path.\");\n+\n+        // If it is a single file:\n+        if (ftpFile.length == 1 && ftpRemoteFilePath.contains(ftpFile[0].getName())) {\n+            String saveFilePath = Paths.get(ftpLocalFilePath, ftpFile[0].getName()).toString();\n+            boolean download = downloadSingleFile(ftpClient, ftpRemoteFilePath, saveFilePath);\n+            if (download) {\n+                getOut().println(\"Succeed to download the file \" + ftpRemoteFilePath + \" to \" + ftpLocalFilePath);\n+            } else {\n+                getOut().println(\"COULD NOT download the file: \" + ftpRemoteFilePath);\n+            }\n+\n+            // If the file is a zip, and ftpExtractArchive is set to true\n+            if (ftpExtractArchive && ftpRemoteFilePath.endsWith(\".zip\")) {\n+                extractArchive(saveFilePath);\n+            }\n+        } // If it is a folder, download all its contents recursively\n+        else {\n+            ftpClient.changeWorkingDirectory(ftpRemoteFilePath);\n+            downloadDirectory(ftpClient, \"\", \"\", ftpLocalFilePath);\n+        }\n+        getOut().println(\"END Import file(s) from FTP.\");\n+    }\n+\n+    private void extractArchive(String saveFilePath) throws IOException {\n+        try (ZipFile zipFile = new ZipFile(saveFilePath)) {\n+            // Create an enumeration of the entries in the zip file\n+            Enumeration zipFileEntries = zipFile.entries();\n+            // Process each entry\n+            while (zipFileEntries.hasMoreElements()) {\n+                // grab a zip file entry\n+                ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();\n+                Path path = Paths.get(ftpLocalFilePath, entry.getName());\n+                getOut().println(\"path: \" + path.toString());\n+                if (entry.isDirectory()) {\n+                    Files.createDirectories(path);\n+                } else {\n+                    Path parentDir = path.getParent();\n+                    getOut().println(\"parentDir of path: \" + parentDir.toString());\n+                    if (!Files.exists(parentDir)) {\n+                        Files.createDirectories(parentDir);\n+                    }\n+                    if (Files.exists(path)) {\n+                        new File(path.toString()).delete();\n+                    }\n+\n+                    Files.copy(zipFile.getInputStream(entry), path);\n+                }\n+            }\n+        }\n+    }\n+\n+    private void FtpPut(FTPClient ftpClient) throws IOException, IllegalArgumentException {\n+        if (ftpLocalFilePath.isEmpty()) {\n+            throw new IllegalArgumentException(\"The ftp local file path variable is empty. Please, enter a value.\");\n+        }\n+        getOut().println(\"Exporting file(s) from \" + ftpLocalFilePath + \" to \" + ftpRemoteFilePath);\n+        File localFile = new File(ftpLocalFilePath);\n+        // if the ftp remote file path does not exist then it is created.\n+        if (!checkDirectoryExists(ftpClient, ftpRemoteFilePath)) {\n+            ftpClient.makeDirectory(ftpRemoteFilePath);\n+            getOut().println(ftpRemoteFilePath + \" does not exist. Therefore, it is created.\");\n+        } else {\n+            getOut().println(ftpRemoteFilePath + \" exists.\");\n+        }\n+        // If it is a single file:\n+        if (localFile.isFile()) {\n+            getOut().println(\"A single FILE to upload\");\n+            ftpClient.changeWorkingDirectory(ftpRemoteFilePath);\n+            boolean uploaded = uploadSingleFile(ftpClient, ftpLocalFilePath, localFile.getName());\n+            if (uploaded) {\n+                getOut().println(\"UPLOADED a file to: \" + Paths.get(ftpRemoteFilePath, localFile.getName()));\n+            } else {\n+                getOut().println(\"COULD NOT upload the file: \" + ftpLocalFilePath);\n+            }\n+        }\n+        // If it is a folder, upload all its contents recursively\n+        else if (localFile.isDirectory()) {\n+            getOut().println(\"A DIRECTORY to upload\");\n+            ftpClient.changeWorkingDirectory(ftpRemoteFilePath);\n+            uploadDirectory(ftpClient, \"\", ftpLocalFilePath, \"\");\n+        } else {\n+            throw new IllegalArgumentException(localFile + \" not found. Please, enter a valid path.\");\n+        }\n+        getOut().println(\"END Export file(s) to FTP.\");\n+    }\n+\n+    private void uploadDirectory(FTPClient ftpClient, String remoteDirPath, String localParentDir,\n+            String remoteParentDir) throws IOException {\n+\n+        getOut().println(\"LISTING directory: \" + localParentDir);\n+\n+        File localDir = new File(localParentDir);\n+        File[] subFiles = localDir.listFiles();\n+        if (subFiles != null && subFiles.length > 0) {\n+            for (File item : subFiles) {\n+                uploadSubFilesDirectory(ftpClient, remoteDirPath, localParentDir, remoteParentDir, item);\n+            }\n+        }\n+    }\n+\n+    private void uploadSubFilesDirectory(FTPClient ftpClient, String remoteDirPath, String localParentDir,\n+            String remoteParentDir, File item) throws IOException {\n+        String remoteFilePath = Paths.get(remoteDirPath, remoteParentDir, item.getName()).toString();\n+        if (remoteParentDir.equals(\"\")) {\n+            remoteFilePath = Paths.get(remoteDirPath, item.getName()).toString();\n+        }\n+\n+        if (item.isFile()) {\n+            // upload the file\n+            String localFilePath = item.getAbsolutePath();\n+            getOut().println(\"About to upload the file: \" + localFilePath);\n+            boolean uploaded = uploadSingleFile(ftpClient, localFilePath, remoteFilePath);\n+            if (uploaded) {\n+                getOut().println(\"UPLOADED a file to: \" + remoteFilePath);\n+            } else {\n+                getOut().println(\"COULD NOT upload the file: \" + localFilePath);\n+            }\n+        } else {\n+            // create directory on the server\n+            boolean created = ftpClient.makeDirectory(remoteFilePath);\n+            if (created) {\n+                getOut().println(\"CREATED the directory: \" + remoteFilePath);\n+            } else {\n+                getOut().println(\"COULD NOT create the directory: \" + remoteFilePath);\n+            }\n+\n+            // upload the sub directory\n+            String parent = Paths.get(remoteParentDir, item.getName()).toString();\n+            if (remoteParentDir.equals(\"\")) {\n+                parent = item.getName();\n+            }\n+\n+            localParentDir = item.getAbsolutePath();\n+            uploadDirectory(ftpClient, remoteDirPath, localParentDir, parent);\n+        }\n+    }\n+\n+    private boolean uploadSingleFile(FTPClient ftpClient, String localFilePath, String remoteFilePath)\n+            throws IOException {\n+        File localFile = new File(localFilePath);\n+\n+        InputStream inputStream = new FileInputStream(localFile);\n+        try {\n+            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);\n+            return ftpClient.storeFile(remoteFilePath, inputStream);\n+        } finally {\n+            inputStream.close();\n+        }\n+    }\n+\n+    private boolean checkDirectoryExists(FTPClient ftpClient, String dirPath) throws IOException {\n+        ftpClient.changeWorkingDirectory(dirPath);\n+        int returnCode = ftpClient.getReplyCode();\n+        if (returnCode == 550) {\n+            return false;\n+        }\n+        return true;\n+    }\n+\n+    private void downloadDirectory(FTPClient ftpClient, String parentDir, String currentDir, String saveDir)\n+            throws IOException {\n+        String dirToList = parentDir;\n+        if (!currentDir.equals(\"\")) {\n+            dirToList = Paths.get(dirToList, currentDir).toString();\n+        }\n+        FTPFile[] subFiles = ftpClient.listFiles(dirToList);\n+        if (subFiles != null && subFiles.length > 0) {\n+            for (FTPFile aFile : subFiles) {\n+                String currentFileName = aFile.getName();\n+                if (currentFileName.equals(\".\") || currentFileName.equals(\"..\")) {\n+                    // skip parent directory and the directory itself\n+                    continue;\n+                }\n+                String filePath = Paths.get(parentDir, currentDir, currentFileName).toString();\n+                if (currentDir.equals(\"\")) {\n+                    filePath = Paths.get(parentDir, currentFileName).toString();\n+                }\n+                String newDirPath = Paths.get(saveDir, parentDir, currentDir, currentFileName).toString();\n+                if (currentDir.equals(\"\")) {\n+                    newDirPath = Paths.get(saveDir, parentDir, currentFileName).toString();\n+                }\n+\n+                downloadSubFilesDirectory(ftpClient, newDirPath, filePath, saveDir, dirToList, currentFileName, aFile);\n+            }\n+        }\n+    }\n+\n+    private void downloadSubFilesDirectory(FTPClient ftpClient, String newDirPath, String filePath, String saveDir,\n+            String dirToList, String currentFileName, FTPFile aFile) throws IOException {\n+        if (aFile.isDirectory()) {\n+            // create the directory in saveDir\n+            File newDir = new File(newDirPath);\n+            boolean created = newDir.mkdirs();\n+            if (created) {\n+                getOut().println(\"CREATED the directory: \" + newDirPath);\n+            } else {\n+                getOut().println(\"COULD NOT create the directory: \" + newDirPath);\n+            }\n+\n+            // download the sub directory\n+            downloadDirectory(ftpClient, dirToList, currentFileName, saveDir);\n+        } else {\n+            // download the file\n+            boolean success = downloadSingleFile(ftpClient, filePath, newDirPath);\n+            if (success) {\n+                getOut().println(\"DOWNLOADED the file: \" + filePath);\n+            } else {\n+                getOut().println(\"COULD NOT download the file: \" + filePath);\n+            }\n+        }\n+    }\n+\n+    private boolean downloadSingleFile(FTPClient ftpClient, String remoteFilePath, String savePath) throws IOException {\n+        getOut().println(\"About to download the file: \" + remoteFilePath);\n+        File downloadFile = new File(savePath);\n+\n+        File parentDir = downloadFile.getParentFile();\n+        if (parentDir != null && !parentDir.exists()) {\n+            parentDir.mkdirs();\n+        }\n+        OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(downloadFile));\n+\n+        try {","path":"scheduler/scheduler-examples/src/main/java/org/ow2/proactive/scheduler/examples/FTPConnector.java","position":380,"original_position":380,"commit_id":"d2119181f361bc68f454f7d3e6653930478d39c9","original_commit_id":"d2119181f361bc68f454f7d3e6653930478d39c9","user":{"login":"Mykhailenko","id":761558,"avatar_url":"https://avatars3.githubusercontent.com/u/761558?v=4","gravatar_id":"","url":"https://api.github.com/users/Mykhailenko","html_url":"https://github.com/Mykhailenko","followers_url":"https://api.github.com/users/Mykhailenko/followers","following_url":"https://api.github.com/users/Mykhailenko/following{/other_user}","gists_url":"https://api.github.com/users/Mykhailenko/gists{/gist_id}","starred_url":"https://api.github.com/users/Mykhailenko/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mykhailenko/subscriptions","organizations_url":"https://api.github.com/users/Mykhailenko/orgs","repos_url":"https://api.github.com/users/Mykhailenko/repos","events_url":"https://api.github.com/users/Mykhailenko/events{/privacy}","received_events_url":"https://api.github.com/users/Mykhailenko/received_events","type":"User","site_admin":false},"body":"could be autoclosable here ","created_at":"2018-03-12T13:49:00Z","updated_at":"2018-03-12T13:49:00Z","html_url":"https://github.com/ow2-proactive/scheduling/pull/3162#discussion_r173801625","pull_request_url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/comments/173801625"},"html":{"href":"https://github.com/ow2-proactive/scheduling/pull/3162#discussion_r173801625"},"pull_request":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162"}}},"pull_request":{"url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162","id":174053729,"html_url":"https://github.com/ow2-proactive/scheduling/pull/3162","diff_url":"https://github.com/ow2-proactive/scheduling/pull/3162.diff","patch_url":"https://github.com/ow2-proactive/scheduling/pull/3162.patch","issue_url":"https://api.github.com/repos/ow2-proactive/scheduling/issues/3162","number":3162,"state":"open","locked":false,"title":"add ftp connector task to scheduler examples project","user":{"login":"Aminelouati","id":29118247,"avatar_url":"https://avatars1.githubusercontent.com/u/29118247?v=4","gravatar_id":"","url":"https://api.github.com/users/Aminelouati","html_url":"https://github.com/Aminelouati","followers_url":"https://api.github.com/users/Aminelouati/followers","following_url":"https://api.github.com/users/Aminelouati/following{/other_user}","gists_url":"https://api.github.com/users/Aminelouati/gists{/gist_id}","starred_url":"https://api.github.com/users/Aminelouati/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Aminelouati/subscriptions","organizations_url":"https://api.github.com/users/Aminelouati/orgs","repos_url":"https://api.github.com/users/Aminelouati/repos","events_url":"https://api.github.com/users/Aminelouati/events{/privacy}","received_events_url":"https://api.github.com/users/Aminelouati/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-03-09T16:48:40Z","updated_at":"2018-03-12T13:49:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2d0c4c258b5ca5638a3ae3bd3554daf2c0da62ee","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162/commits","review_comments_url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162/comments","review_comment_url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ow2-proactive/scheduling/issues/3162/comments","statuses_url":"https://api.github.com/repos/ow2-proactive/scheduling/statuses/d2119181f361bc68f454f7d3e6653930478d39c9","head":{"label":"Aminelouati:ftp-connector","ref":"ftp-connector","sha":"d2119181f361bc68f454f7d3e6653930478d39c9","user":{"login":"Aminelouati","id":29118247,"avatar_url":"https://avatars1.githubusercontent.com/u/29118247?v=4","gravatar_id":"","url":"https://api.github.com/users/Aminelouati","html_url":"https://github.com/Aminelouati","followers_url":"https://api.github.com/users/Aminelouati/followers","following_url":"https://api.github.com/users/Aminelouati/following{/other_user}","gists_url":"https://api.github.com/users/Aminelouati/gists{/gist_id}","starred_url":"https://api.github.com/users/Aminelouati/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Aminelouati/subscriptions","organizations_url":"https://api.github.com/users/Aminelouati/orgs","repos_url":"https://api.github.com/users/Aminelouati/repos","events_url":"https://api.github.com/users/Aminelouati/events{/privacy}","received_events_url":"https://api.github.com/users/Aminelouati/received_events","type":"User","site_admin":false},"repo":{"id":124109253,"name":"scheduling","full_name":"Aminelouati/scheduling","owner":{"login":"Aminelouati","id":29118247,"avatar_url":"https://avatars1.githubusercontent.com/u/29118247?v=4","gravatar_id":"","url":"https://api.github.com/users/Aminelouati","html_url":"https://github.com/Aminelouati","followers_url":"https://api.github.com/users/Aminelouati/followers","following_url":"https://api.github.com/users/Aminelouati/following{/other_user}","gists_url":"https://api.github.com/users/Aminelouati/gists{/gist_id}","starred_url":"https://api.github.com/users/Aminelouati/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Aminelouati/subscriptions","organizations_url":"https://api.github.com/users/Aminelouati/orgs","repos_url":"https://api.github.com/users/Aminelouati/repos","events_url":"https://api.github.com/users/Aminelouati/events{/privacy}","received_events_url":"https://api.github.com/users/Aminelouati/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Aminelouati/scheduling","description":"Multi-platform Scheduling and Workflows Engine","fork":true,"url":"https://api.github.com/repos/Aminelouati/scheduling","forks_url":"https://api.github.com/repos/Aminelouati/scheduling/forks","keys_url":"https://api.github.com/repos/Aminelouati/scheduling/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Aminelouati/scheduling/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Aminelouati/scheduling/teams","hooks_url":"https://api.github.com/repos/Aminelouati/scheduling/hooks","issue_events_url":"https://api.github.com/repos/Aminelouati/scheduling/issues/events{/number}","events_url":"https://api.github.com/repos/Aminelouati/scheduling/events","assignees_url":"https://api.github.com/repos/Aminelouati/scheduling/assignees{/user}","branches_url":"https://api.github.com/repos/Aminelouati/scheduling/branches{/branch}","tags_url":"https://api.github.com/repos/Aminelouati/scheduling/tags","blobs_url":"https://api.github.com/repos/Aminelouati/scheduling/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Aminelouati/scheduling/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Aminelouati/scheduling/git/refs{/sha}","trees_url":"https://api.github.com/repos/Aminelouati/scheduling/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Aminelouati/scheduling/statuses/{sha}","languages_url":"https://api.github.com/repos/Aminelouati/scheduling/languages","stargazers_url":"https://api.github.com/repos/Aminelouati/scheduling/stargazers","contributors_url":"https://api.github.com/repos/Aminelouati/scheduling/contributors","subscribers_url":"https://api.github.com/repos/Aminelouati/scheduling/subscribers","subscription_url":"https://api.github.com/repos/Aminelouati/scheduling/subscription","commits_url":"https://api.github.com/repos/Aminelouati/scheduling/commits{/sha}","git_commits_url":"https://api.github.com/repos/Aminelouati/scheduling/git/commits{/sha}","comments_url":"https://api.github.com/repos/Aminelouati/scheduling/comments{/number}","issue_comment_url":"https://api.github.com/repos/Aminelouati/scheduling/issues/comments{/number}","contents_url":"https://api.github.com/repos/Aminelouati/scheduling/contents/{+path}","compare_url":"https://api.github.com/repos/Aminelouati/scheduling/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Aminelouati/scheduling/merges","archive_url":"https://api.github.com/repos/Aminelouati/scheduling/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Aminelouati/scheduling/downloads","issues_url":"https://api.github.com/repos/Aminelouati/scheduling/issues{/number}","pulls_url":"https://api.github.com/repos/Aminelouati/scheduling/pulls{/number}","milestones_url":"https://api.github.com/repos/Aminelouati/scheduling/milestones{/number}","notifications_url":"https://api.github.com/repos/Aminelouati/scheduling/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Aminelouati/scheduling/labels{/name}","releases_url":"https://api.github.com/repos/Aminelouati/scheduling/releases{/id}","deployments_url":"https://api.github.com/repos/Aminelouati/scheduling/deployments","created_at":"2018-03-06T16:51:28Z","updated_at":"2018-03-06T16:51:36Z","pushed_at":"2018-03-10T17:31:52Z","git_url":"git://github.com/Aminelouati/scheduling.git","ssh_url":"git@github.com:Aminelouati/scheduling.git","clone_url":"https://github.com/Aminelouati/scheduling.git","svn_url":"https://github.com/Aminelouati/scheduling","homepage":"http://www.activeeon.com/workflows-scheduling","size":73264,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"ow2-proactive:master","ref":"master","sha":"af39209fd1438045c4fccc0040afdc3e165cf22d","user":{"login":"ow2-proactive","id":11553998,"avatar_url":"https://avatars1.githubusercontent.com/u/11553998?v=4","gravatar_id":"","url":"https://api.github.com/users/ow2-proactive","html_url":"https://github.com/ow2-proactive","followers_url":"https://api.github.com/users/ow2-proactive/followers","following_url":"https://api.github.com/users/ow2-proactive/following{/other_user}","gists_url":"https://api.github.com/users/ow2-proactive/gists{/gist_id}","starred_url":"https://api.github.com/users/ow2-proactive/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ow2-proactive/subscriptions","organizations_url":"https://api.github.com/users/ow2-proactive/orgs","repos_url":"https://api.github.com/users/ow2-proactive/repos","events_url":"https://api.github.com/users/ow2-proactive/events{/privacy}","received_events_url":"https://api.github.com/users/ow2-proactive/received_events","type":"Organization","site_admin":false},"repo":{"id":35725079,"name":"scheduling","full_name":"ow2-proactive/scheduling","owner":{"login":"ow2-proactive","id":11553998,"avatar_url":"https://avatars1.githubusercontent.com/u/11553998?v=4","gravatar_id":"","url":"https://api.github.com/users/ow2-proactive","html_url":"https://github.com/ow2-proactive","followers_url":"https://api.github.com/users/ow2-proactive/followers","following_url":"https://api.github.com/users/ow2-proactive/following{/other_user}","gists_url":"https://api.github.com/users/ow2-proactive/gists{/gist_id}","starred_url":"https://api.github.com/users/ow2-proactive/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ow2-proactive/subscriptions","organizations_url":"https://api.github.com/users/ow2-proactive/orgs","repos_url":"https://api.github.com/users/ow2-proactive/repos","events_url":"https://api.github.com/users/ow2-proactive/events{/privacy}","received_events_url":"https://api.github.com/users/ow2-proactive/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ow2-proactive/scheduling","description":"Multi-platform Scheduling and Workflows Engine","fork":false,"url":"https://api.github.com/repos/ow2-proactive/scheduling","forks_url":"https://api.github.com/repos/ow2-proactive/scheduling/forks","keys_url":"https://api.github.com/repos/ow2-proactive/scheduling/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ow2-proactive/scheduling/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ow2-proactive/scheduling/teams","hooks_url":"https://api.github.com/repos/ow2-proactive/scheduling/hooks","issue_events_url":"https://api.github.com/repos/ow2-proactive/scheduling/issues/events{/number}","events_url":"https://api.github.com/repos/ow2-proactive/scheduling/events","assignees_url":"https://api.github.com/repos/ow2-proactive/scheduling/assignees{/user}","branches_url":"https://api.github.com/repos/ow2-proactive/scheduling/branches{/branch}","tags_url":"https://api.github.com/repos/ow2-proactive/scheduling/tags","blobs_url":"https://api.github.com/repos/ow2-proactive/scheduling/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ow2-proactive/scheduling/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ow2-proactive/scheduling/git/refs{/sha}","trees_url":"https://api.github.com/repos/ow2-proactive/scheduling/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ow2-proactive/scheduling/statuses/{sha}","languages_url":"https://api.github.com/repos/ow2-proactive/scheduling/languages","stargazers_url":"https://api.github.com/repos/ow2-proactive/scheduling/stargazers","contributors_url":"https://api.github.com/repos/ow2-proactive/scheduling/contributors","subscribers_url":"https://api.github.com/repos/ow2-proactive/scheduling/subscribers","subscription_url":"https://api.github.com/repos/ow2-proactive/scheduling/subscription","commits_url":"https://api.github.com/repos/ow2-proactive/scheduling/commits{/sha}","git_commits_url":"https://api.github.com/repos/ow2-proactive/scheduling/git/commits{/sha}","comments_url":"https://api.github.com/repos/ow2-proactive/scheduling/comments{/number}","issue_comment_url":"https://api.github.com/repos/ow2-proactive/scheduling/issues/comments{/number}","contents_url":"https://api.github.com/repos/ow2-proactive/scheduling/contents/{+path}","compare_url":"https://api.github.com/repos/ow2-proactive/scheduling/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ow2-proactive/scheduling/merges","archive_url":"https://api.github.com/repos/ow2-proactive/scheduling/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ow2-proactive/scheduling/downloads","issues_url":"https://api.github.com/repos/ow2-proactive/scheduling/issues{/number}","pulls_url":"https://api.github.com/repos/ow2-proactive/scheduling/pulls{/number}","milestones_url":"https://api.github.com/repos/ow2-proactive/scheduling/milestones{/number}","notifications_url":"https://api.github.com/repos/ow2-proactive/scheduling/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ow2-proactive/scheduling/labels{/name}","releases_url":"https://api.github.com/repos/ow2-proactive/scheduling/releases{/id}","deployments_url":"https://api.github.com/repos/ow2-proactive/scheduling/deployments","created_at":"2015-05-16T13:38:55Z","updated_at":"2018-03-12T13:38:32Z","pushed_at":"2018-03-12T13:02:52Z","git_url":"git://github.com/ow2-proactive/scheduling.git","ssh_url":"git@github.com:ow2-proactive/scheduling.git","clone_url":"https://github.com/ow2-proactive/scheduling.git","svn_url":"https://github.com/ow2-proactive/scheduling","homepage":"http://www.activeeon.com/workflows-scheduling","size":73257,"stargazers_count":23,"watchers_count":23,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":38,"mirror_url":null,"archived":false,"open_issues_count":170,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0"},"forks":38,"open_issues":170,"watchers":23,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162"},"html":{"href":"https://github.com/ow2-proactive/scheduling/pull/3162"},"issue":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/issues/3162"},"comments":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/issues/3162/comments"},"review_comments":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162/comments"},"review_comment":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/pulls/3162/commits"},"statuses":{"href":"https://api.github.com/repos/ow2-proactive/scheduling/statuses/d2119181f361bc68f454f7d3e6653930478d39c9"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"Mykhailenko"}}