From 0bd94b7fce08f197896231275c113895fb8a8aad Mon Sep 17 00:00:00 2001 From: Lukas Roth Date: Fri, 16 Dec 2016 16:06:36 +0100 Subject: [PATCH 1/4] HTTP Interface - added an new HTTP Interface - added a new 'file_save', 'file_list', and 'file_delete' command - added a new 'add_dependency' and 'remove_dependency' command - added new test cases for the new commands and the HTTP Interface --- ant/build.gant | 8 +- .../core/command/file/FileDeleteCommand.java | 104 +++++++ .../core/command/file/FileListCommand.java | 142 +++++++++ .../file/FileListCommandException.java | 15 + .../core/command/file/FileSaveCommand.java | 141 +++++++++ .../org/eclim/plugin/core/messages.properties | 14 + .../org/eclim/plugin/core/util/PathUtil.java | 97 ++++++ .../plugin/core/util/PathUtilException.java | 16 + org.eclim.core/test/eclimrc.test | 3 + .../test/junit/org/eclim/EclimHTTPClient.java | 221 ++++++++++++++ .../test/junit/org/eclim/EclimTestCase.java | 14 +- .../test/junit/org/eclim/HTTPServerTest.java | 197 +++++++++++++ .../command/file/FileListCommandTest.java | 135 +++++++++ .../file/FileSaveDeleteCommandTest.java | 168 +++++++++++ .../dependency/AddDependencyCommand.java | 96 ++++++ .../dependency/ClasspathFileManipulator.java | 34 +++ .../ClasspathFileManipulatorException.java | 38 +++ .../dependency/RemoveDependencyCommand.java | 97 ++++++ .../SimpleClasspathFileManipulator.java | 189 ++++++++++++ .../org/eclim/plugin/jdt/messages.properties | 5 + .../JarUploadRemoveCommandTest.java | 212 ++++++++++++++ .../SimpleClasspathFileManipulatorTest.java | 162 +++++++++++ .../testfiles/OriginalClasspath.txt | 8 + .../jdt/command/dependency/testfiles/add1.txt | 8 + .../dependency/testfiles/add1_expected.txt | 7 + .../dependency/testfiles/addRemove1.txt | 7 + .../testfiles/addRemove1_expected.txt | 7 + .../testfiles/addRemoveMultipleTimes1.txt | 7 + .../addRemoveMultipleTimes1_expected.txt | 7 + .../dependency/testfiles/addTwoTimesSame1.txt | 8 + .../testfiles/addTwoTimesSame1_expected.txt | 7 + .../command/dependency/testfiles/remove1.txt | 7 + .../dependency/testfiles/remove1_expected.txt | 7 + .../testfiles/removeNotExisting1.txt | 8 + .../testfiles/removeNotExisting1_expected.txt | 7 + .../org/eclim/command/CommandException.java | 70 +++++ .../java/org/eclim/command/CommandLine.java | 11 + org.eclim/java/org/eclim/command/Options.java | 2 + .../java/org/eclim/eclipse/EclimDaemon.java | 30 +- .../java/org/eclim/http/CommandCaller.java | 246 ++++++++++++++++ .../eclim/http/CommandCallerException.java | 61 ++++ .../org/eclim/http/EclimHTTPResponse.java | 61 ++++ org.eclim/java/org/eclim/http/HTTPServer.java | 275 ++++++++++++++++++ .../org/eclim/http/HTTPServerException.java | 46 +++ .../eclim/http/InvalidCommandException.java | 15 + org.eclim/java/org/eclim/logging/Logger.java | 32 +- org.eclim/java/org/eclim/messages.properties | 8 + org.eclim/plugin.properties | 3 + 48 files changed, 3049 insertions(+), 14 deletions(-) create mode 100644 org.eclim.core/java/org/eclim/plugin/core/command/file/FileDeleteCommand.java create mode 100644 org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java create mode 100644 org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommandException.java create mode 100644 org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java create mode 100644 org.eclim.core/java/org/eclim/plugin/core/util/PathUtil.java create mode 100644 org.eclim.core/java/org/eclim/plugin/core/util/PathUtilException.java create mode 100644 org.eclim.core/test/junit/org/eclim/EclimHTTPClient.java create mode 100644 org.eclim.core/test/junit/org/eclim/HTTPServerTest.java create mode 100644 org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileListCommandTest.java create mode 100644 org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileSaveDeleteCommandTest.java create mode 100644 org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java create mode 100644 org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java create mode 100644 org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java create mode 100644 org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java create mode 100644 org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt create mode 100644 org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt create mode 100644 org.eclim/java/org/eclim/command/CommandException.java create mode 100644 org.eclim/java/org/eclim/http/CommandCaller.java create mode 100644 org.eclim/java/org/eclim/http/CommandCallerException.java create mode 100644 org.eclim/java/org/eclim/http/EclimHTTPResponse.java create mode 100644 org.eclim/java/org/eclim/http/HTTPServer.java create mode 100644 org.eclim/java/org/eclim/http/HTTPServerException.java create mode 100644 org.eclim/java/org/eclim/http/InvalidCommandException.java diff --git a/ant/build.gant b/ant/build.gant index eb96726b2..075abf2e4 100644 --- a/ant/build.gant +++ b/ant/build.gant @@ -1113,7 +1113,10 @@ junit = { pluginName -> compile("${pluginName}/test/junit", "build/test/junit/classes/${pluginName}"){ ant.compilerarg(value: '-Xlint:-options') ant.include(name: '**/*.java') - ant.classpath{ant.pathelement(path: '${build.classes}/org.eclim')} + ant.classpath{ + ant.pathelement(path: '${build.classes}/org.eclim') + ant.pathelement(path: '${build.classes}/org.eclim.jdt') + } } ant.path(id: 'junit'){ @@ -1130,6 +1133,7 @@ junit = { pluginName -> ant.path(refid: 'junit') ant.pathelement(path: "build/test/junit/classes/${pluginName}") ant.pathelement(path: '${build.classes}/org.eclim') + ant.pathelement(path: '${build.classes}/org.eclim.jdt') ant.fileset(dir: 'org.eclim/lib', includes: '*.jar', excludes: 'ant-*.jar') } ant.formatter(type: 'xml') @@ -1141,6 +1145,8 @@ junit = { pluginName -> ant.sysproperty(key: 'eclipse.home', value: '${eclipse}') ant.sysproperty(key: 'eclim.version', value: '${eclim.version}') ant.sysproperty(key: 'eclimd.port', value: '${nailgun.server.port}') + ant.sysproperty(key: 'http.server.port', value: '${http.server.port}') + ant.sysproperty(key: 'http.server.host', value: '${http.server.host}') } } diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileDeleteCommand.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileDeleteCommand.java new file mode 100644 index 000000000..945851e9e --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileDeleteCommand.java @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.command.file; + +import java.io.File; + +import org.eclim.Services; +import org.eclim.annotation.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandException.ErrorType; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.command.AbstractCommand; +import org.eclim.plugin.core.util.PathUtil; +import org.eclim.plugin.core.util.PathUtilException; + +@Command( + name = "file_delete", + options = + "REQUIRED f relativeFilePath ARG," + + "REQUIRED p project ARG" +) + +/** + * Command to delete a file. + * + * The relativeFilePath specifies the path relative to the + * project where the file/folder should be deleted. + * + * If the file is a directory, also all subdirectories will be deleted. + * + * Warning: you might have to update the project after calling this + * command. + * + * + * @author Lukas Roth + * + */ +public class FileDeleteCommand extends AbstractCommand +{ + private static final Logger logger = Logger.getLogger(FileDeleteCommand.class); + + @Override + public Object execute(CommandLine commandLine) + throws Exception + { + String relativeFilePath = commandLine.getValue(Options.FILE_OPTION); + String projectName = commandLine.getValue(Options.PROJECT_OPTION); + return fileDelete(relativeFilePath, projectName); + } + + public Object fileDelete(String relativeFilePath, String projectName) + { + try { + PathUtil.checkPathForEscaping(relativeFilePath); + } catch (PathUtilException e) { + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + String absoluteFilePath; + try { + absoluteFilePath = PathUtil.getAbsolutePath(projectName, relativeFilePath); + } catch (PathUtilException e) { + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + return deleteFileOnFileSystem(absoluteFilePath, relativeFilePath); + } + + private Object deleteFileOnFileSystem(String absoluteFilePath, String filePath) + { + File file = new File(absoluteFilePath); + if (!file.exists()) { + String message = Services.getMessage("file.delete.not.found", filePath); + logger.error(message); + return new CommandException(message, ErrorType.CLIENT_ERROR); + } + if (file.isDirectory()) { + org.eclim.util.file.FileUtils.deleteDirectory(file); + return Services.getMessage("file.delete.directory.deleted", filePath); + } else { + if (file.delete()) { + return Services.getMessage("file.delete.success", filePath); + } else { + String message = Services.getMessage("file.delete.error", filePath); + logger.error(Services.getMessage("file.delete.error", filePath)); + return new CommandException(message, ErrorType.SYSTEM_ERROR); + } + } + } +} diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java new file mode 100644 index 000000000..010cf6e22 --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.command.file; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +import org.eclim.Services; +import org.eclim.annotation.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.command.AbstractCommand; +import org.eclim.plugin.core.util.PathUtil; +import org.eclim.plugin.core.util.PathUtilException; + +@Command( + name = "file_list", + options = + "REQUIRED f relativeFilePath ARG," + + "REQUIRED p project ARG," + + "OPTIONAL r recursiveFlag ARG" +) + +/** + * Command to get a list of all files and folders in the folder + * {@code relativeFilePath} which is a relative path to the project + * {@code project}. + * + * Example response: + * + * myFolder/ + * mySecFolder/ + * myFile.txt + * + * + * If the {@code recursiveFlag} argument is set to true the command traverses + * all the folders inside the {@code relativeFilePath} recursively and returns + * all the folders inclusive all subfolders and subfiles. + * + * Example response with {@code recursiveFlag} set to true: + * + * myFolder/ + * myFolder/mySubFolder/ + * myFolder/mySubFolder/mySubFile.txt + * mySecFolder/ + * myFile.txt + * + * @author Lukas Roth + * + */ +public class FileListCommand extends AbstractCommand +{ + private static final Logger logger = Logger.getLogger(FileListCommand.class); + private List result; + + @Override + public Object execute(CommandLine commandLine) + throws Exception + { + String relativeFilePath = commandLine.getValue(Options.FILE_OPTION); + String projectName = commandLine.getValue(Options.PROJECT_OPTION); + String recursiveFlag = commandLine.getValue(Options.RECURSIVE_OPTION); + boolean recursive = Boolean.parseBoolean(recursiveFlag); + try { + return fileList(projectName, relativeFilePath, recursive); + } catch (FileListCommandException e) { + return new CommandException(e, CommandException.ErrorType.CLIENT_ERROR); + } + } + + public List fileList(String projectName, String relativeFilePath, + boolean recursive) + throws FileListCommandException + { + validatePath(relativeFilePath); + File baseFile = getRootPath(projectName, relativeFilePath); + if (!baseFile.exists()) { + throw new FileListCommandException( + Services.getMessage("file.list.no.file", relativeFilePath, projectName)); + } + result = new LinkedList(); + traverseChildNodes(baseFile, null, recursive); + return result; + } + + private void validatePath(String path) + throws FileListCommandException + { + try { + PathUtil.checkPathForEscaping(path); + } catch (PathUtilException e) { + throw new FileListCommandException( + Services.getMessage("file.path.error.illegal.path", path), e); + } + } + + private void traverseChildNodes(File baseFile, String path, boolean recursive) + { + File[] childNodes = baseFile.listFiles(); + for (File child : childNodes) { + String childPath = (path == null) ? child.getName() : + (path + "/" + child.getName()); + if (child.isFile()) { + result.add(childPath); + } else if (child.isDirectory()) { + result.add(childPath + "/"); + if (recursive) { + traverseChildNodes(child, childPath, recursive); + } + } + } + } + + private File getRootPath(String projectName, String relativeFilePath) + throws FileListCommandException + { + try { + return new File(PathUtil.getAbsolutePath(projectName, relativeFilePath)); + } catch (PathUtilException e) { + throw new FileListCommandException( + Services.getMessage("file.list.absolute.path.error", projectName), e); + } + } + +} diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommandException.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommandException.java new file mode 100644 index 000000000..7d20f5106 --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommandException.java @@ -0,0 +1,15 @@ +package org.eclim.plugin.core.command.file; + +public class FileListCommandException extends Exception +{ + + public FileListCommandException(String message, Exception exception) + { + super(message, exception); + } + + public FileListCommandException(String message) + { + super(message); + } +} diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java new file mode 100644 index 000000000..fa0541e81 --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java @@ -0,0 +1,141 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.command.file; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.eclim.Services; +import org.eclim.annotation.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandException.ErrorType; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.command.AbstractCommand; +import org.eclim.plugin.core.util.PathUtil; +import org.eclim.plugin.core.util.PathUtilException; + +@Command( + name = "file_save", + options = + "REQUIRED f relativeFilePath ARG," + + "REQUIRED p project ARG," + + "REQUIRED c content ARG" +) +/** + * Command to save a file with content c. + * + * The content can either be a String or a InputStream. + * + * The relativeFilePath specifies the path relative to the + * project where the content should be saved. + * + * If there is no file at the specified location the file gets created + * (including parent directories). If there is already a file the content of the + * file will be overwritten. + * + * Warning: you might have to update the project after calling this + * command. + * + * @author Lukas Roth + * + */ +public class FileSaveCommand extends AbstractCommand +{ + private static final Logger logger = Logger.getLogger(FileSaveCommand.class); + + @Override + public Object execute(CommandLine commandLine) + throws Exception + { + String relativeFilePath = commandLine.getValue(Options.FILE_OPTION); + String projectName = commandLine.getValue(Options.PROJECT_OPTION); + Object fileIn = commandLine.getRawValue(Options.CONTENT_OPTION); + InputStream fileStream; + if (fileIn == null) { + fileStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)); + } else if (fileIn instanceof String) { + fileStream = new ByteArrayInputStream( + ((String) fileIn).getBytes(StandardCharsets.UTF_8)); + } else if (fileIn instanceof InputStream) { + fileStream = (InputStream) fileIn; + } else { + String message = Services.getMessage("file.save.content.wrong.type"); + logger.error(message); + return new CommandException(message, ErrorType.SYSTEM_ERROR); + } + return fileSave(relativeFilePath, projectName, fileStream); + } + + public Object fileSave(String relativeFilePath, String projectName, + InputStream fileContent) + { + try { + PathUtil.checkPathForEscaping(relativeFilePath); + } catch (PathUtilException e) { + logger.error("File path is not valid", e); + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + String absoluteFilePath; + try { + absoluteFilePath = PathUtil.getAbsolutePath(projectName, relativeFilePath); + } catch (PathUtilException e) { + logger.error("Could not get the absolute path", e); + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + try { + writeToFileSystem(absoluteFilePath, fileContent); + return Services.getMessage("file.save.success", relativeFilePath); + } catch (IOException e) { + String message = Services.getMessage("file.save.error.io", relativeFilePath); + logger.error(message, e); + return new CommandException(message, ErrorType.SYSTEM_ERROR); + } + } + + private void writeToFileSystem(String absoluteFilePath, InputStream fileContent) + throws IOException + { + File file = new File(absoluteFilePath); + if (file.exists()) { + logger.debug("Overwriting file at " + absoluteFilePath); + } + file.getParentFile().mkdirs(); + copyInputStreamToFile(fileContent, file); + } + + // copied from + // http://stackoverflow.com/questions/43157/easy-way-to-write-contents-of-a-java-inputstream-to-an-outputstream + private void copyInputStreamToFile(InputStream in, File file) + throws IOException + { + OutputStream out = new FileOutputStream(file); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + } +} diff --git a/org.eclim.core/java/org/eclim/plugin/core/messages.properties b/org.eclim.core/java/org/eclim/plugin/core/messages.properties index 6658965f3..d93cf670c 100644 --- a/org.eclim.core/java/org/eclim/plugin/core/messages.properties +++ b/org.eclim.core/java/org/eclim/plugin/core/messages.properties @@ -18,6 +18,20 @@ nature.alias.not.found=No nature alias ''{0}'' found. settings.updated=Settings updated. setting.not.set=Required setting ''{0}'' has not been set. +# file messages +file.save.success=File saved at location ''{0}''. +file.save.error.io=File could not be saved to the file system ''{0}''. +file.save.content.wrong.type=The file content has a wrong type. The file content is not a String nor an InputStream. +file.delete.not.found=No file at location ''{0}''. +file.delete.directory.deleted=Folder ''{0}'' deleted. +file.delete.success=File ''{0}'' deleted. +file.delete.error=Could not delete the file at location ''{0}''. +file.path.error.illegal.path=Illegal path ''{0}''. +file.path.error.project.not.exist=Project ''{0}'' does not exist. +file.project.update.error=Project ''{0}'' could not be updated. +file.list.absolute.path.error=Could not get absolute path of the project ''{0}''. +file.list.no.file=No file at ''{0}'' in project ''{1}''. + # project status messages. project.created=Created project ''{0}''. project.imported=Imported project ''{0}''. diff --git a/org.eclim.core/java/org/eclim/plugin/core/util/PathUtil.java b/org.eclim.core/java/org/eclim/plugin/core/util/PathUtil.java new file mode 100644 index 000000000..530637400 --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/util/PathUtil.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.util; + +import org.eclim.Services; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.util.ProjectUtils; +import org.eclipse.core.resources.IProject; + +/** + * Util class which helps to generate absolute paths and checks paths for not + * allowed content. + * + * @author Lukas Roth + * + */ +public class PathUtil +{ + private static final Logger logger = Logger.getLogger(PathUtil.class); + + /** + * Checks if the path contains ".." as a substring. If so throw a + * PathUtilException. + * + * @param path + * @throws PathUtilException + */ + public static void checkPathForEscaping(String path) + throws PathUtilException + { + if (path.contains("..")) { + String errMsg = Services.getMessage("file.path.error.illegal.path", path); + throw new PathUtilException(errMsg); + } + } + + /** + * Returns the absolute file path generated form the projectName + * and the relative file path to the project filePath. + * + * Throws PathUtilException if the project can not be found. + * + * Example: If there is an exampleProject at location + * '/home/user/projects/exampleProject' then + * 'getAbsoluteFilePath(exampleProject, /path/to/my/file.txt)' returns + * '/home/user/projects/exampleProject/path/to/my/file.txt' + * + * @param projectName + * @param filePath + * @return absoluteFilePath Absolute file path. + * @throws PathUtilException + */ + public static String getAbsolutePath(String projectName, String filePath) + throws PathUtilException + { + return getProjectPath(projectName) + "/" + filePath; + } + + /** + * Returns the absolute path of the project projectName. + * + * @param projectName + * @return absoluteProjectPath Absolute path of the project. + * @throws PathUtilException + * Throws PathUtilException the project does not exist. + */ + public static String getProjectPath(String projectName) + throws PathUtilException + { + IProject project; + try { + project = ProjectUtils.getProject(projectName); + } catch (Exception e) { + throw new PathUtilException( + Services.getMessage("file.path.error.project.not.exist", projectName), e); + } + if (project == null || project.getLocation() == null) { + throw new PathUtilException( + Services.getMessage("file.path.error.project.not.exist", projectName)); + } + return project.getLocation().toOSString(); + } +} diff --git a/org.eclim.core/java/org/eclim/plugin/core/util/PathUtilException.java b/org.eclim.core/java/org/eclim/plugin/core/util/PathUtilException.java new file mode 100644 index 000000000..beb306568 --- /dev/null +++ b/org.eclim.core/java/org/eclim/plugin/core/util/PathUtilException.java @@ -0,0 +1,16 @@ +package org.eclim.plugin.core.util; + +public class PathUtilException extends Exception +{ + private static final long serialVersionUID = 1564613385435L; + + public PathUtilException(String message) + { + super(message); + } + + public PathUtilException(String message, Exception exception) + { + super(message, exception); + } +} diff --git a/org.eclim.core/test/eclimrc.test b/org.eclim.core/test/eclimrc.test index 58a018c83..70cd4c87b 100644 --- a/org.eclim.core/test/eclimrc.test +++ b/org.eclim.core/test/eclimrc.test @@ -2,6 +2,9 @@ osgi.instance.area.default=@user.home/workspace.unittest nailgun.server.port=9092 +http.server.enabled=true +http.server.port=9997 +http.server.host=localhost # increase heap space -Xmx1g diff --git a/org.eclim.core/test/junit/org/eclim/EclimHTTPClient.java b/org.eclim.core/test/junit/org/eclim/EclimHTTPClient.java new file mode 100644 index 000000000..68d819a2e --- /dev/null +++ b/org.eclim.core/test/junit/org/eclim/EclimHTTPClient.java @@ -0,0 +1,221 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.eclim.http.EclimHTTPResponse; + +import com.google.gson.Gson; + +/** + * Client to call eclim over the HTTP Interface. + * + * @author Lukas Roth + * + */ +public class EclimHTTPClient +{ + + private static final String APPLICATION_FORM = "application/x-www-form-urlencoded"; + private static final String PORT = System.getProperty("http.server.port"); + private static final String HOST = System.getProperty("http.server.host", "localhost"); + private static String eclimAddress = "http://" + HOST + ":" + PORT + "/eclim/command/"; + + /** + * Calls eclim over a HTTP POST request with the parameters specified in the + * argument map parameters. + * + * Example parameters: parameters = {"command":"projects"} + * + * @param parameters + * The eclim parameters + * @return EcimHTTPResponse The response of eclim + * @throws IOException + */ + public EclimHTTPResponse post(Map parameters) + throws IOException + { + byte[] postData = urlEncodeUTF8(parameters).getBytes(StandardCharsets.UTF_8); + String unparsedResponse = post(postData, getEclimAddress(), APPLICATION_FORM); + return (new Gson()).fromJson(unparsedResponse, EclimHTTPResponse.class); + } + + /** + * Calls eclim over a HTTP POST request with the parameters specified in the + * argument map parameters. The file will be posted to the body + * while the parameters will passed as query parameters. + * + * Example parameters: InputStream file = new FileInputStream("my.jar"); + * parameters = {"command":"jar_upload", "p":"exampleProject", + * "f","lib/my.jar"} + * post(parameters, file); + * + * @param parameters + * The eclim parameters. + * @param file + * The content of the inputstream inside the file + * parameter will be in the body of the request to eclim. + * @param conatentType + * The content type of your post request. + * @return EcimHTTPResponse The response of eclim + * @throws IOException + */ + public EclimHTTPResponse post(Map parameters, InputStream file, String contentType) + throws IOException + { + byte[] postData = toByteArray(file); + String unparsedResponse = post(postData, getEclimAddress(parameters), + contentType); + return (new Gson()).fromJson(unparsedResponse, EclimHTTPResponse.class); + } + + /** + * Calls eclim over a HTTP GET request with the parameters specified in the + * argument map parameters. + * + * Example parameters: parameters = {"command":"projects"} + * + * @param parameters + * @return EcimHTTPResponse The response of eclim + * @throws IOException + */ + public EclimHTTPResponse get(Map parameters) + throws IOException + { + String unparsedResponse = get(getEclimAddress(parameters)); + return (new Gson()).fromJson(unparsedResponse, EclimHTTPResponse.class); + } + + protected String post(byte[] postData, String request, String contentType) + throws IOException + { + int postDataLength = postData.length; + URL url = new URL(request); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setInstanceFollowRedirects(false); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", contentType); + connection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); + connection.setUseCaches(false); + + DataOutputStream wr = null; + try { + wr = new DataOutputStream(connection.getOutputStream()); + wr.write(postData); + } finally { + wr.close(); + } + + InputStream result; + if (200 <= connection.getResponseCode() && connection.getResponseCode() <= 299) { + result = connection.getInputStream(); + }else{ + result = connection.getErrorStream(); + } + return convertStreamToString(result); + } + + private String get(String requestAddress) + throws IOException + { + URL url = new URL(requestAddress); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + InputStream result; + if (200 <= connection.getResponseCode() && connection.getResponseCode() <= 299) { + result = connection.getInputStream(); + }else{ + result = connection.getErrorStream(); + } + return convertStreamToString(result); } + + private String getEclimAddress(Map parameters) + { + return getEclimAddress() + "?" + urlEncodeUTF8(parameters); + } + + public static String getEclimAddress() + { + return eclimAddress; + } + + public static void setEclimAddress(String eclimAddress) + { + EclimHTTPClient.eclimAddress = eclimAddress; + } + + // copied from + // http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string + private static String convertStreamToString(java.io.InputStream is) + { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + // copied from + // http://stackoverflow.com/questions/1264709/convert-inputstream-to-byte-array-in-java + private static byte[] toByteArray(InputStream inputStream) + throws IOException + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + return buffer.toByteArray(); + } + + // copied from + // http://stackoverflow.com/questions/2809877/how-to-convert-map-to-url-query-string + private static String urlEncodeUTF8(String s) + { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new UnsupportedOperationException(e); + } + } + + // copied from + // http://stackoverflow.com/questions/2809877/how-to-convert-map-to-url-query-string + private static String urlEncodeUTF8(Map map) + { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(String.format("%s=%s", urlEncodeUTF8(entry.getKey().toString()), + urlEncodeUTF8(entry.getValue().toString()))); + } + return sb.toString(); + } +} diff --git a/org.eclim.core/test/junit/org/eclim/EclimTestCase.java b/org.eclim.core/test/junit/org/eclim/EclimTestCase.java index 47c6210e0..81a4998fc 100644 --- a/org.eclim.core/test/junit/org/eclim/EclimTestCase.java +++ b/org.eclim.core/test/junit/org/eclim/EclimTestCase.java @@ -34,13 +34,13 @@ */ public class EclimTestCase { - private HashMap modified = new HashMap(); + private HashMap modified = new HashMap(); @After public void resetModified() - throws Exception + throws Exception { - for (Map.Entry entry : modified.entrySet()){ + for (Map.Entry entry : modified.entrySet()) { String[] parts = StringUtils.split(entry.getKey(), '|'); String project = parts[0]; String file = parts[1]; @@ -57,13 +57,15 @@ public void resetModified() * supplied project. After the test runs, that file will be restored to it's * original contents. * - * @param project The name of the project the file resides in. - * @param file The project relative path of the file. + * @param project + * The name of the project the file resides in. + * @param file + * The project relative path of the file. */ protected void modifies(String project, String file) { String key = project + '|' + file; - if (!modified.containsKey(key)){ + if (!modified.containsKey(key)) { String contents = Eclim.fileToString(project, file); modified.put(key, contents); } diff --git a/org.eclim.core/test/junit/org/eclim/HTTPServerTest.java b/org.eclim.core/test/junit/org/eclim/HTTPServerTest.java new file mode 100644 index 000000000..8afcc197d --- /dev/null +++ b/org.eclim.core/test/junit/org/eclim/HTTPServerTest.java @@ -0,0 +1,197 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.eclim.http.EclimHTTPResponse; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests the HTTP Interface of eclim. + * + * @author Lukas Roth + * + */ +public class HTTPServerTest extends EclimHTTPClient +{ + private String eclimAddress = EclimHTTPClient.getEclimAddress(); + private EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + + @Test + public void getExpected() + throws IOException + { + Map projectsParams = new HashMap(); + projectsParams.put("command", "projects"); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + EclimHTTPResponse result = eclimHTTPClient.get(projectsParams); + assertProjectsExist(result); + } + + @Test + public void postExpected() + throws IOException + { + Map projectsParams = new HashMap(); + projectsParams.put("command", "projects"); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + EclimHTTPResponse result = eclimHTTPClient.post(projectsParams); + assertProjectsExist(result); + } + + private void assertProjectsExist(EclimHTTPResponse result) + { + Assert.assertTrue( + result.getResult().contains("\"name\":\"RemoteSystemsTempFiles\"")); + Assert.assertTrue(result.getResult().contains("\"name\":\"eclim_unit_test\"")); + Assert.assertTrue( + result.getResult().contains("\"name\":\"eclim_unit_test_java\"")); + } + + @Test + public void unsupportedMethod() + throws IOException + { + URL url = new URL(eclimAddress + "?command=projects"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("DELETE"); + connection.connect(); + Assert.assertEquals(405, connection.getResponseCode()); + } + + @Test + public void doubleArgumentKey() + throws IOException + { + String requestAddress = EclimHTTPClient.getEclimAddress() + + "?command=a&command=a"; + URL url = new URL(requestAddress); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + InputStream result; + if (200 <= connection.getResponseCode() + && connection.getResponseCode() <= 299) { + result = connection.getInputStream(); + } else { + result = connection.getErrorStream(); + } + + String stringResult = convertStreamToString(result); + Assert.assertTrue(stringResult.contains("Each argument needs an unique key.")); + } + + // copied from + // http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string + private static String convertStreamToString(java.io.InputStream is) + { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + @Test + public void postFile() + throws IOException + { + String testFilePath = "testFile.txt"; + File testFile = new File( + Eclim.getProjectPath(Eclim.TEST_PROJECT) + "/" + testFilePath); + + Map saveStreamParams = new HashMap(); + saveStreamParams.put("command", "file_save"); + saveStreamParams.put("p", Eclim.TEST_PROJECT); + saveStreamParams.put("f", testFilePath); + InputStream file = new ByteArrayInputStream( + "Test Content".getBytes(StandardCharsets.UTF_8)); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + eclimHTTPClient.post(saveStreamParams, file, "application/txt"); + + deleteFile(testFilePath, testFile); + } + + private void deleteFile(String testFilePath, File testFile) + { + String deleteResult = (String) Eclim.execute(new String[] { "file_delete", "-p", + Eclim.TEST_PROJECT, "-f", testFilePath }); + assertEquals("File '" + testFilePath + "' deleted.", deleteResult); + assertTrue("File deleted", !testFile.exists()); + } + + @Test + public void postFileNotAllowedParameterKey() + throws IOException + { + Map saveStreamParams = new HashMap(); + saveStreamParams.put("command", "file_save"); + saveStreamParams.put("s", "something"); + InputStream file = new ByteArrayInputStream( + "Test Content".getBytes(StandardCharsets.UTF_8)); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + EclimHTTPResponse result = eclimHTTPClient.post(saveStreamParams, file, + "application/txt"); + assertCorrectStatusCode(result, 400); + Assert.assertTrue( + result.getResult().contains("Could not create a command line out of")); + } + + @Test + public void postFileNotAllowedParameter() + throws IOException + { + Map saveStreamParams = new HashMap(); + saveStreamParams.put("command", "file_save"); + // The 'c' command key is not allowed in a file post + saveStreamParams.put("c", "something"); + InputStream file = new ByteArrayInputStream( + "Test content".getBytes(StandardCharsets.UTF_8)); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + EclimHTTPResponse result = eclimHTTPClient.post(saveStreamParams, file, + "application/txt"); + assertCorrectStatusCode(result, 400); + Assert.assertTrue(result.getResult().contains( + "key is not allowed in a command which passes a file in the body of the request.")); + } + + @Test + public void commandException() + throws IOException + { + Map parameters = new HashMap(); + parameters.put("asdfasdf", ""); + EclimHTTPResponse response = eclimHTTPClient.get(parameters); + Assert.assertTrue( + response.getResult().contains("Could not create a command line out of")); + } + + private void assertCorrectStatusCode(EclimHTTPResponse result, int expected) + { + Assert.assertEquals("Correct error code", expected, result.getStatusCode()); + } +} diff --git a/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileListCommandTest.java b/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileListCommandTest.java new file mode 100644 index 000000000..b7eb9cf36 --- /dev/null +++ b/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileListCommandTest.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.command.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclim.Eclim; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * This test tests the file_list command. + * + * @author Lukas Roth + * + */ +@SuppressWarnings("unchecked") +public class FileListCommandTest +{ + private static final String BASE_FOLDER = "fileListCommandTest_temp/"; + private static final String TEST_FILE_1_PATH = BASE_FOLDER + + "my/example/folder/testFile1.txt"; + private static final String TEST_FILE_2_PATH = BASE_FOLDER + "testFile2.txt"; + private static final String TEST_CONTENT = "some content"; + private static final String NOT_EXISTING_PROJECT = "someNotExistingProject"; + private static final String ERROR_PROJECT_DOES_NOT_EXIST = "Could not get absolute path of the project '" + + NOT_EXISTING_PROJECT + "'."; + private static final String ILLEGAL_PATH = "asdf/../asdfw"; + private static final String ERROR_ILLEGAL_PATH = "Illegal path '" + ILLEGAL_PATH + + "'."; + private static final String NOT_EXISTING_FILE_PATH = "egrgg97z2hg9s9gh9z20w0gu02/"; + + @Before + public void setup() + { + setupExampleFiles(); + } + + @After + public void cleanUp() + { + Eclim.execute(new String[] { "file_delete", "-p", Eclim.TEST_PROJECT, "-f", + BASE_FOLDER }); + assertTrue("File deleted", + !(new File(Eclim.getProjectPath(Eclim.TEST_PROJECT) + "/" + BASE_FOLDER)) + .exists()); + } + + private void setupExampleFiles() + { + saveExampleFile(TEST_FILE_1_PATH); + saveExampleFile(TEST_FILE_2_PATH); + } + + private void saveExampleFile(String path) + { + String createResult = (String) Eclim.execute(new String[] { "file_save", "-p", + Eclim.TEST_PROJECT, "-f", path, "-c", TEST_CONTENT }); + assertEquals("File saved at location '" + path + "'.", createResult); + } + + @Test + public void testNormal() + { + List fileListResult = (List) Eclim.execute( + new String[] { "file_list", "-p", Eclim.TEST_PROJECT, "-f", BASE_FOLDER }); + List expectedResult = new ArrayList(); + expectedResult.add("my/"); + expectedResult.add("testFile2.txt"); + Assert.assertEquals(fileListResult, expectedResult); + } + + @Test + public void testRecursive() + { + List fileListResult = (List) Eclim.execute(new String[] { + "file_list", "-p", Eclim.TEST_PROJECT, "-f", BASE_FOLDER, "-r", "true" }); + List expectedResult = new ArrayList(); + expectedResult.add("my/"); + expectedResult.add("my/example/"); + expectedResult.add("my/example/folder/"); + expectedResult.add("my/example/folder/testFile1.txt"); + expectedResult.add("testFile2.txt"); + Assert.assertEquals(fileListResult, expectedResult); + } + + @Test + public void notExistingProject() + { + Map createResult = (Map) Eclim + .execute(new String[] { "file_list", "-p", NOT_EXISTING_PROJECT, "-f", + NOT_EXISTING_FILE_PATH }); + assertEquals(ERROR_PROJECT_DOES_NOT_EXIST, createResult.get("message")); + } + + @Test + public void illegalPath() + { + Map createResult = (Map) Eclim.execute( + new String[] { "file_list", "-p", Eclim.TEST_PROJECT, "-f", ILLEGAL_PATH }); + assertEquals(ERROR_ILLEGAL_PATH, createResult.get("message")); + } + + @Test + public void nonExistingPath() + { + Map createResult = (Map) Eclim + .execute(new String[] { "file_list", "-p", Eclim.TEST_PROJECT, "-f", + NOT_EXISTING_FILE_PATH }); + assertEquals("No file at '" + NOT_EXISTING_FILE_PATH + "' in project '" + + Eclim.TEST_PROJECT + "'.", createResult.get("message")); + } +} \ No newline at end of file diff --git a/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileSaveDeleteCommandTest.java b/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileSaveDeleteCommandTest.java new file mode 100644 index 000000000..ece68277f --- /dev/null +++ b/org.eclim.core/test/junit/org/eclim/plugin/core/command/file/FileSaveDeleteCommandTest.java @@ -0,0 +1,168 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.core.command.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.eclim.Eclim; +import org.eclim.EclimHTTPClient; +import org.junit.Test; + +/** + * The class {@code FileSaveDeleteCommandTest} tests the file_save and + * file_delete commands of eclim. + * + * It uses the HTTP Interface of eclim to test the commands. + * + * For the file save it tests both, the stream parameter over the HTTP Interface + * and the string parameter. + * + * @author Lukas Roth + * + */ +public class FileSaveDeleteCommandTest +{ + private static final String TEST_FILE_PATH = "testFile.txt"; + private static final File TEST_FILE = new File( + Eclim.getProjectPath(Eclim.TEST_PROJECT) + "/" + TEST_FILE_PATH); + private static final String TEST_FOLDER = "/testDir/"; + private static final String TEST_CONTENT = "some content"; + private static final String NOT_EXISTING_PROJECT = "someNotExistingProject"; + private static final String WRONG_PATH = "some/path/containing/../bla/bli"; + private static final String ERROR_PROJECT_DOES_NOT_EXIST = "Project '" + + NOT_EXISTING_PROJECT + "' does not exist."; + private static final String ERROR_ILLEGAL_PATH = "Illegal path '" + WRONG_PATH + + "'."; + + @Test + public void saveString() + throws Exception + { + saveStringFile(); + assertFileIsThere(); + deleteFile(); + } + + private void saveStringFile() + { + String createResult = (String) Eclim.execute(new String[] { "file_save", "-p", + Eclim.TEST_PROJECT, "-f", TEST_FILE_PATH, "-c", TEST_CONTENT }); + assertEquals("File saved at location '" + TEST_FILE_PATH + "'.", createResult); + } + + @Test + public void saveStream() + throws IOException + { + saveStreamFile(); + assertFileIsThere(); + deleteFile(); + } + + private void saveStreamFile() + throws IOException + { + Map saveStreamParams = new HashMap(); + saveStreamParams.put("command", "file_save"); + saveStreamParams.put("p", Eclim.TEST_PROJECT); + saveStreamParams.put("f", TEST_FILE_PATH); + InputStream file = new ByteArrayInputStream( + TEST_CONTENT.getBytes(StandardCharsets.UTF_8)); + EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + eclimHTTPClient.post(saveStreamParams, file, "application/txt"); + } + + private void assertFileIsThere() + throws IOException + { + assertTrue("File exists", TEST_FILE.exists()); + assertEquals("Right content", readFile(TEST_FILE.getPath()), TEST_CONTENT); + } + + private void deleteFile() + { + String deleteResult = (String) Eclim.execute(new String[] { "file_delete", "-p", + Eclim.TEST_PROJECT, "-f", TEST_FILE_PATH }); + assertEquals("File '" + TEST_FILE_PATH + "' deleted.", deleteResult); + assertTrue("File deleted", !TEST_FILE.exists()); + } + + @Test + public void wrongPathSave() + { + Map createResult = (Map) Eclim + .execute(new String[] { "file_save", "-p", Eclim.TEST_PROJECT, "-f", + WRONG_PATH, "-c", TEST_CONTENT }); + assertEquals(ERROR_ILLEGAL_PATH, createResult.get("message")); + } + + @Test + public void wrongPathDelete() + { + Map createResult = (Map) Eclim.execute( + new String[] { "file_delete", "-p", Eclim.TEST_PROJECT, "-f", WRONG_PATH }); + assertEquals(ERROR_ILLEGAL_PATH, createResult.get("message")); + } + + @Test + public void notExistingProjectSave() + { + Map createResult = (Map) Eclim + .execute(new String[] { "file_save", "-p", NOT_EXISTING_PROJECT, "-f", + TEST_FILE_PATH, "-c", TEST_CONTENT }); + assertEquals(ERROR_PROJECT_DOES_NOT_EXIST, createResult.get("message")); + } + + @Test + public void notExistingProjectDelete() + { + Map createResult = (Map) Eclim + .execute(new String[] { "file_delete", "-p", NOT_EXISTING_PROJECT, "-f", + TEST_FILE_PATH }); + assertEquals(ERROR_PROJECT_DOES_NOT_EXIST, createResult.get("message")); + } + + @Test + public void deleteFolder() + { + File f = new File(Eclim.getProjectPath(Eclim.TEST_PROJECT) + "/" + TEST_FOLDER); + f.mkdirs(); + String createResult = (String) Eclim.execute(new String[] { "file_delete", "-p", + Eclim.TEST_PROJECT, "-f", TEST_FOLDER }); + assertEquals("Folder '" + TEST_FOLDER + "' deleted.", createResult); + assertTrue("Folder deleted", !f.exists()); + } + + private static String readFile(String path) + throws IOException + { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, Charset.defaultCharset()); + } +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java new file mode 100644 index 000000000..59a87b2e4 --- /dev/null +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +import org.eclim.Services; +import org.eclim.annotation.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandException.ErrorType; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.command.AbstractCommand; +import org.eclim.plugin.core.util.PathUtil; +import org.eclim.plugin.core.util.PathUtilException; + +@Command( + name = "add_dependency", + options = + "REQUIRED p project ARG," + + "REQUIRED f relativeFilePath ARG" +) + +/** + * Command to add a dependency entry to your eclipse '.classpath' file of the + * project. + * + * Example use case: You first upload a example.jar file to the file system over + * the {@code FileSaveCommand} and then add the file path to your .classpath + * file such that eclipse knows that there is a new dependency. + * + * Warning: you have to update the project after calling this + * command. You can do this over the 'project_update' command. + * + * @author Lukas Roth + * + */ +public class AddDependencyCommand extends AbstractCommand +{ + private static final Logger logger = Logger + .getLogger(AddDependencyCommand.class); + + @Override + public Object execute(CommandLine commandLine) + throws Exception + { + String projectName = commandLine.getValue(Options.PROJECT_OPTION); + String relativeFilePath = commandLine.getValue(Options.FILE_OPTION); + return addDependency(projectName, relativeFilePath); + } + + public Object addDependency(String projectName, String relativeFilePath) + { + String absoluteFilePath; + String projectPath; + try { + absoluteFilePath = PathUtil.getAbsolutePath(projectName, relativeFilePath); + projectPath = PathUtil.getProjectPath(projectName); + } catch (PathUtilException e) { + logger.error("Could not get the absolute project path", e); + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + try { + callClasspathFileManipulator(absoluteFilePath, + projectPath + "/" + ".classpath"); + } catch (ClasspathFileManipulatorException e) { + logger.error(Services.getMessage("dependency.classpath.error"), e); + return new CommandException(Services.getMessage("dependency.classpath.error"), + ErrorType.SYSTEM_ERROR); + } + return Services.getMessage("dependency.upload.jar.success", relativeFilePath); + } + + private void callClasspathFileManipulator(String dependencyFilePath, + String classpathFilePath) + throws ClasspathFileManipulatorException + { + SimpleClasspathFileManipulator classpathFileManipulator = + new SimpleClasspathFileManipulator(); + classpathFileManipulator.addJarDependency(dependencyFilePath, + classpathFilePath); + } +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java new file mode 100644 index 000000000..b8bd40cdc --- /dev/null +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +/** + * The {@code ClasspathFileManipulator} Interface should be implemented by all + * the classes which can manipulate an eclipse classpath file such that one can + * add/remove a jar dependencies. + * + * @author Lukas Roth + * + */ +public interface ClasspathFileManipulator +{ + void addJarDependency(String dependencyFilePath, String classPathFilePath) + throws ClasspathFileManipulatorException; + + void removeJarDependency(String dependencyFilePath, String classPathFilePath) + throws ClasspathFileManipulatorException; +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java new file mode 100644 index 000000000..4cb23be91 --- /dev/null +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +/** + * Exception thrown by the {@code ClasspathFileManipulator}. + * + * @author Lukas Roth + * + */ +public class ClasspathFileManipulatorException extends Exception +{ + private static final long serialVersionUID = -1988234200200654186L; + + public ClasspathFileManipulatorException(String message) + { + super(message); + } + + public ClasspathFileManipulatorException(String message, Exception exception) + { + super(message, exception); + } +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java new file mode 100644 index 000000000..f70f04953 --- /dev/null +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +import org.eclim.Services; +import org.eclim.annotation.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandException.ErrorType; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclim.plugin.core.command.AbstractCommand; +import org.eclim.plugin.core.util.PathUtil; +import org.eclim.plugin.core.util.PathUtilException; + +@Command( + name = "remove_dependency", + options = + "REQUIRED p project ARG," + + "REQUIRED f relativeFilePath ARG" +) + +/** + * Command to remove a dependency entry from your eclipse '.classpath' file of + * the project. + * + * Example use case: You first delete a example.jar file from the file system + * over the {@code FileDeleteCommand} and then remove the file path from your + * .classpath file such that eclipse knows that the dependency is no more there. + * + * Warning: you have to update the project after calling this + * command. Do this over the 'project_update' command. + * + * @author Lukas Roth + * + */ +public class RemoveDependencyCommand extends AbstractCommand +{ + private static final Logger logger = Logger + .getLogger(RemoveDependencyCommand.class); + + @Override + public Object execute(CommandLine commandLine) + throws Exception + { + String projectName = commandLine.getValue(Options.PROJECT_OPTION); + String relativeFilePath = commandLine.getValue(Options.FILE_OPTION); + return removeDependency(projectName, relativeFilePath); + } + + public Object removeDependency(String projectName, + String relativeFilePath) + { + String absoluteFilePath; + String projectPath; + try { + absoluteFilePath = PathUtil.getAbsolutePath(projectName, relativeFilePath); + projectPath = PathUtil.getProjectPath(projectName); + } catch (PathUtilException e) { + return new CommandException(e, ErrorType.CLIENT_ERROR); + } + try { + callClasspathFileManipulator(absoluteFilePath, + projectPath + "/" + ".classpath"); + } catch (ClasspathFileManipulatorException e) { + logger.error(Services.getMessage("dependency.classpath.error"), e); + return new CommandException( + Services.getMessage("dependency.classpath.error"), + ErrorType.SYSTEM_ERROR); + } + return Services.getMessage("dependency.remove.jar.success", relativeFilePath); + } + + private void callClasspathFileManipulator(String dependencyFilePath, + String classpathFilePath) + throws ClasspathFileManipulatorException + { + SimpleClasspathFileManipulator classpathFileManipulator + = new SimpleClasspathFileManipulator(); + classpathFileManipulator.removeJarDependency(dependencyFilePath, + classpathFilePath); + } +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java new file mode 100644 index 000000000..d31f4c1fb --- /dev/null +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java @@ -0,0 +1,189 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclim.logging.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * + * The {@code SimpleClasspathFileManipulator} manipulates the .classpath file of an + * eclipse project. + * + * It allows a user to + * - add a dependency file + * - remove a dependency file + * + * @author Lukas Roth + * + */ +public class SimpleClasspathFileManipulator implements ClasspathFileManipulator +{ + private final Logger logger = Logger + .getLogger(SimpleClasspathFileManipulator.class); + private static final String ERROR_LOG_ENTRY = + "Error while manipulating the eclipse classpath"; + private static final String CLASS_PATH_NODE_NAME = "classpath"; + private static final String KIND_ATTRIBUTE = "kind"; + private static final String KIND_ATTRIBUTE_LIB = "lib"; + private static final String PATH_ATTRIBUTE = "path"; + private static final String ELEMENT_NAME_CLASS_PATH_ENTRY = "classpathentry"; + + /** + * Adds a jar dependency entry for the jar at position + * dependencyFilePath to the .classpath file (which is at path + * classPathFilePath). After this you need to ensure that eclipse + * refreshes the dependencies! + */ + @Override + public void addJarDependency(String dependencyFilePath, String classPathFilePath) + throws ClasspathFileManipulatorException + { + Document doc = parseXML(classPathFilePath); + Node classPathNode = getClassPathNode(doc, CLASS_PATH_NODE_NAME); + + List foundNodes = findChildNodes(dependencyFilePath, classPathNode); + if (!foundNodes.isEmpty()) { + logger.debug("There is already an entry '" + dependencyFilePath + + "' --> it will not be added a second time."); + return; + } + Element classPathEntry = doc.createElement(ELEMENT_NAME_CLASS_PATH_ENTRY); + classPathEntry.setAttribute(KIND_ATTRIBUTE, KIND_ATTRIBUTE_LIB); + classPathEntry.setAttribute(PATH_ATTRIBUTE, dependencyFilePath); + classPathNode.appendChild(classPathEntry); + + writeXmlBackToFile(doc, classPathFilePath); + } + + /** + * Removes all jar dependency entries for the jar at position + * dependencyFilePath from the .classpath file (which is at path + * classPathFilePath). After this you need to ensure that eclipse + * refreshes the dependencies! + */ + @Override + public void removeJarDependency(String dependencyFilePath, + String classPathFilePath) + throws ClasspathFileManipulatorException + { + Document document = parseXML(classPathFilePath); + Node classPathNode = getClassPathNode(document, CLASS_PATH_NODE_NAME); + List childesToRemove = findChildNodes(dependencyFilePath, classPathNode); + if (childesToRemove.size() != 1) { + logger.debug("There were " + childesToRemove.size() + + " nodes which all fit to the dependency entry which gets removed now"); + } + removeChildNodes(classPathNode, childesToRemove); + writeXmlBackToFile(document, classPathFilePath); + } + + private Node getClassPathNode(Document doc, String tagName) + throws ClasspathFileManipulatorException + { + if (doc != null) { + NodeList temp = doc.getElementsByTagName(tagName); + if (temp != null) { + Node retNode = temp.item(0); + if (retNode != null) { + return retNode; + } + } + } + throw new ClasspathFileManipulatorException( + "Could not find the node: " + tagName); + } + + private List findChildNodes(String dependencyFilePath, Node classPathNode) + { + NodeList childes = classPathNode.getChildNodes(); + List childesToRemove = new ArrayList(); + for (int i = 0; i < childes.getLength(); i++) { + Node child = childes.item(i); + if (child.getNodeName().equals(ELEMENT_NAME_CLASS_PATH_ENTRY) && + child.hasAttributes()) + { + NamedNodeMap attributeMap = child.getAttributes(); + Node kind = attributeMap.getNamedItem(KIND_ATTRIBUTE); + Node path = attributeMap.getNamedItem(PATH_ATTRIBUTE); + if (kind != null && path != null && + kind.getNodeValue().equals(KIND_ATTRIBUTE_LIB) && + path.getNodeValue().equals(dependencyFilePath)) + { + childesToRemove.add(child); + } + } + } + return childesToRemove; + } + + private void removeChildNodes(Node classpath, List childesToRemove) + { + for (Node child : childesToRemove) { + classpath.removeChild(child); + } + } + + private void writeXmlBackToFile(Document document, String filePath) + throws ClasspathFileManipulatorException + { + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer; + transformer = transformerFactory.newTransformer(); + + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(new File(filePath)); + transformer.transform(source, result); + } catch (Exception e) { + throw new ClasspathFileManipulatorException(ERROR_LOG_ENTRY, e); + } + } + + private Document parseXML(String document) + throws ClasspathFileManipulatorException + { + try { + logger.debug("Parsing XML file: " + document); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new File(document)); + if (doc != null) { + return doc; + } + throw new ClasspathFileManipulatorException( + "Error parsing XML: doc is null after parsing"); + } catch (Exception e) { + throw new ClasspathFileManipulatorException(ERROR_LOG_ENTRY, e); + } + } +} diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/messages.properties b/org.eclim.jdt/java/org/eclim/plugin/jdt/messages.properties index 5b45dac21..8cc10fe0b 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/messages.properties +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/messages.properties @@ -154,3 +154,8 @@ org.eclipse.jdt.core.codeComplete.deprecationCheck=\ org.eclipse.jdt.core.codeComplete.visibilityCheck=\ Filter out inaccessible members (private, etc) during code completion\n\ (enabled or disabled). + +# Command dependency +dependency.classpath.error=Could not manipulate the eclipse classpath file. +dependency.upload.jar.success=Added the library ''{0}'' to the classpath file of eclipse. +dependency.remove.jar.success=Removed the library ''{0}'' from the classpath file of eclipse. \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java new file mode 100644 index 000000000..2d845317b --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java @@ -0,0 +1,212 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +import static org.junit.Assert.assertTrue; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclim.Eclim; +import org.eclim.EclimHTTPClient; +import org.eclim.http.EclimHTTPResponse; +import org.eclim.plugin.jdt.Jdt; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.gson.JsonParser; + +import junit.framework.Assert; + +/** + * This test we upload a dependency jar, manipulate the classpat file such that + * eclipse knows we uploaded a jar and then remove the dependency jar again. + * + * Test idea: Upload and remove a GSON Library and check that - before the + * upload Gson is not there - after the upload Gson is there - after the remove + * Gson is not there again + * + * We check if the Gson library is correctly uploaded with the help of the + * completion command. --> if we get a completion for GsonB (-> GsonBuilder) we + * know that the dependency was added correctly. + * + * @author Lukas Roth + */ +public class JarUploadRemoveCommandTest +{ + private static final String TEST_FILE = "src/org/eclim/test/exampleClass.java"; + private static final String TEST_FILE_CONTENT = "package org.eclim.test;\nclass A{\nvoid f(){\nGsonB}\n}"; + private static final int TEST_FILE_OFFSET = 48; + private static final String PATH_TO_DEPENDENCY = "lib/gson-1.7.1.jar"; + private static final String DEPENDENCY_LOCATION_ON_FILE_SYSTEM = "org.eclim/lib/gson-1.7.1.jar"; + private EclimHTTPClient eclimHTTPClient = new EclimHTTPClient(); + + @Before + public void setupTestFile() + throws IOException + { + assertTrue("Java project doesn't exist.", + Eclim.projectExists(Jdt.TEST_PROJECT)); + setupCompletionFile(); + } + + @After + public void deleteTestFile() + throws IOException + { + Map parameters = new HashMap(); + parameters.put("command", "file_delete"); + parameters.put("p", Jdt.TEST_PROJECT); + parameters.put("f", TEST_FILE); + eclimHTTPClient.get(parameters); + + updateProject(); + } + + private void setupCompletionFile() + throws IOException + { + createTestFile(); + updateProject(); + } + + private void updateProject() + throws IOException + { + Map parameters = new HashMap(); + parameters.put("command", "project_update"); + parameters.put("p", Jdt.TEST_PROJECT); + eclimHTTPClient.get(parameters); + } + + private void createTestFile() + throws IOException + { + Map parameters = new HashMap(); + parameters.put("command", "file_save"); + parameters.put("p", Jdt.TEST_PROJECT); + parameters.put("f", TEST_FILE); + parameters.put("c", TEST_FILE_CONTENT); + eclimHTTPClient.get(parameters); + } + + @Test + public void jarUploadRemoveTest() + throws IOException + { + assertLibraryNotAvailable(); + uploadJar(); + updateProject(); + assertLibraryAvailable(); + removeJar(); + updateProject(); + assertLibraryNotAvailable(); + } + + private void removeJar() + throws IOException + { + deleteJar(); + removeFromClasspath(); + } + + private void removeFromClasspath() + throws IOException + { + Map uploadJarParams = new HashMap(); + uploadJarParams.put("command", "remove_dependency"); + uploadJarParams.put("p", Jdt.TEST_PROJECT); + uploadJarParams.put("f", PATH_TO_DEPENDENCY); + eclimHTTPClient.post(uploadJarParams); + } + + private void deleteJar() + throws IOException + { + Map saveJarParams = new HashMap(); + saveJarParams.put("command", "file_delete"); + saveJarParams.put("p", Jdt.TEST_PROJECT); + saveJarParams.put("f", PATH_TO_DEPENDENCY); + eclimHTTPClient.post(saveJarParams); + } + + private void uploadJar() + throws IOException + { + saveJar(); + addToClasspath(); + } + + private void addToClasspath() + throws IOException + { + Map uploadJarParams = new HashMap(); + uploadJarParams.put("command", "add_dependency"); + uploadJarParams.put("p", Jdt.TEST_PROJECT); + uploadJarParams.put("f", PATH_TO_DEPENDENCY); + eclimHTTPClient.post(uploadJarParams); + } + + private void saveJar() + throws IOException + { + Map saveJarParams = new HashMap(); + saveJarParams.put("command", "file_save"); + saveJarParams.put("p", Jdt.TEST_PROJECT); + saveJarParams.put("f", PATH_TO_DEPENDENCY); + InputStream file = new FileInputStream(DEPENDENCY_LOCATION_ON_FILE_SYSTEM); + eclimHTTPClient.post(saveJarParams, file, "application/jar"); + } + + private void assertLibraryNotAvailable() + throws IOException + { + List> completions = getCompletions(TEST_FILE_OFFSET); + Assert.assertEquals("No completion should be found for the prefix 'GsonB'", 0, + completions.size()); + } + + private void assertLibraryAvailable() + throws IOException + { + List> completions = getCompletions(TEST_FILE_OFFSET); + Assert.assertEquals("GsonBuilder", + (String) completions.get(0).get("completion")); + } + + @SuppressWarnings("unchecked") + private List> getCompletions(int offset) + throws IOException + { + Map parameters = new HashMap(); + parameters.put("command", "java_complete"); + parameters.put("p", Jdt.TEST_PROJECT); + parameters.put("f", TEST_FILE); + parameters.put("o", Integer.toString(offset)); + parameters.put("e", "utf-8"); + parameters.put("l", "standard"); + EclimHTTPResponse response = eclimHTTPClient.post(parameters); + Map parsedResponse = (Map) Eclim + .toType((new JsonParser()).parse(response.getResult())); + return (List>) parsedResponse.get("completions"); + } +} diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java new file mode 100644 index 000000000..74a8863e1 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.plugin.jdt.command.dependency; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit test for the {@code SimpleClasspathFileManipulator} class. + * + * @author Lukas Roth + * + */ +public class SimpleClasspathFileManipulatorTest +{ + private static final String TEST_FILES_BASE_FOLDER = "org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/"; + private static List cleanupList = new ArrayList(); + private String dependencyLocation1 = "/example/folder/bla.jar"; + private String dependencyLocation2 = "/example/folder/bla2.jar"; + private String dependencyLocation3 = "/example/folder/bla3.jar"; + private String dependencyLocation4 = "/example/folder/bla4.jar"; + ClasspathFileManipulator classpathFileManipulator = new SimpleClasspathFileManipulator();; + + @Test + public void normalAddJarDependencyToClasspath() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/add1.txt"); + classpathFileManipulator.addJarDependency(dependencyLocation1, filepath); + String res = readFile(filepath); + String expectedRes = readFile(getBaseFolder() + "/add1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + @Test + public void normalRemoveJarDependencyToClasspath() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/remove1.txt"); + classpathFileManipulator.removeJarDependency(dependencyLocation1, filepath); + String res = readFile(filepath); + String expectedRes = readFile(getBaseFolder() + "/remove1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + @Test + public void normalAddRemoveJarDependencyToClasspath() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/addRemove1.txt"); + classpathFileManipulator.addJarDependency(dependencyLocation1, filepath); + classpathFileManipulator.removeJarDependency(dependencyLocation1, filepath); + String res = readFile(filepath); + String expectedRes = readFile(getBaseFolder() + "/addRemove1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + @Test + public void addRemoveMultipleTimes() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/addRemoveMultipleTimes1.txt"); + classpathFileManipulator.addJarDependency(dependencyLocation1, filepath); + classpathFileManipulator.addJarDependency(dependencyLocation2, filepath); + classpathFileManipulator.addJarDependency(dependencyLocation3, filepath); + classpathFileManipulator.addJarDependency(dependencyLocation4, filepath); + classpathFileManipulator.removeJarDependency(dependencyLocation1, filepath); + classpathFileManipulator.removeJarDependency(dependencyLocation3, filepath); + String res = readFile(filepath); + String expectedRes = readFile( + getBaseFolder() + "/addRemoveMultipleTimes1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + // we expect that it then does not re-add the classpath to the file + @Test + public void addTwoTimesSameClasspath() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/addTwoTimesSame1.txt"); + classpathFileManipulator.addJarDependency(dependencyLocation1, filepath); + classpathFileManipulator.addJarDependency(dependencyLocation1, filepath); + String res = readFile(filepath); + String expectedRes = readFile( + getBaseFolder() + "/addTwoTimesSame1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + // we expect that it does not crash removing a non existent classpath + @Test + public void removeNotExisting() + throws ClasspathFileManipulatorException, IOException + { + String filepath = getTempCopy(getBaseFolder() + "/removeNotExisting1.txt"); + classpathFileManipulator.removeJarDependency(dependencyLocation1, filepath); + String res = readFile(filepath); + String expectedRes = readFile( + getBaseFolder() + "/removeNotExisting1_expected.txt"); + Assert.assertEquals(expectedRes, res); + } + + static String readFile(String path) + throws IOException + { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, Charset.defaultCharset()); + } + + private static String getBaseFolder() + { + return TEST_FILES_BASE_FOLDER; + } + + private static String getTempCopy(String string) + throws IOException + { + String pathOfCopy = string + "_copy"; + Files.copy((new File(string)).toPath(), (new File(pathOfCopy)).toPath(), + REPLACE_EXISTING); + cleanupList.add(pathOfCopy); + return pathOfCopy; + } + + @After + public void cleanUpCopies() + { + for (String file : cleanupList) { + delete(file); + } + } + + private static void delete(String filepath) + { + File f = new File(filepath); + f.delete(); + } +} diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt new file mode 100644 index 000000000..bed045a6b --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt new file mode 100644 index 000000000..bed045a6b --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt new file mode 100644 index 000000000..45fce9a26 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt new file mode 100644 index 000000000..302b1a929 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt new file mode 100644 index 000000000..302b1a929 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt new file mode 100644 index 000000000..302b1a929 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt new file mode 100644 index 000000000..7c6a2dfe8 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt new file mode 100644 index 000000000..bed045a6b --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt new file mode 100644 index 000000000..45fce9a26 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt new file mode 100644 index 000000000..45fce9a26 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt new file mode 100644 index 000000000..302b1a929 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt new file mode 100644 index 000000000..bed045a6b --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt new file mode 100644 index 000000000..302b1a929 --- /dev/null +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.eclim/java/org/eclim/command/CommandException.java b/org.eclim/java/org/eclim/command/CommandException.java new file mode 100644 index 000000000..b245bfce4 --- /dev/null +++ b/org.eclim/java/org/eclim/command/CommandException.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.command; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * If a command wants to throw an exception and return it over the eclim + * interfaces (NailGun or HTTP) to the client the command can not return the + * exception: If it would return an Exception org.eclim.command.Main.nailMain() + * (line 133 - 139) would crash. --> The commands have the possibility to return + * this {@code CommandException} which then will be returned as a JSON Object. + * + * @author Lukas Roth + * + */ +public class CommandException +{ + /** + * Describes if the error originates from the client (e.g. a project is not + * here which the client passed in the argument) or if it is an internal error + * (e.g. IO Error, we can not write to disk) + */ + public enum ErrorType { + SYSTEM_ERROR, CLIENT_ERROR + } + + /* + * NOSONAR: This fields will be used to create the JSON object. + */ + private final String message; // NOSONAR + private final String stackTrace; // NOSONAR + private final ErrorType errorType; // NOSONAR + + public CommandException(Exception e, ErrorType errorType) + { + this.message = e.getMessage(); + StringWriter buf = new StringWriter(); + e.printStackTrace(new PrintWriter(buf)); + this.stackTrace = buf.toString(); + this.errorType = errorType; + } + + public CommandException(String message, ErrorType errorType) + { + this.message = message; + this.stackTrace = ""; + this.errorType = errorType; + } + + public ErrorType getErrorType() + { + return errorType; + } +} diff --git a/org.eclim/java/org/eclim/command/CommandLine.java b/org.eclim/java/org/eclim/command/CommandLine.java index a5f7d97c5..79ab49b7f 100644 --- a/org.eclim/java/org/eclim/command/CommandLine.java +++ b/org.eclim/java/org/eclim/command/CommandLine.java @@ -233,6 +233,17 @@ public void addOption(String option, String value) options.put(option, value); } + /** + * Adds another option to this command line. + * + * @param option The option. + * @param value The option value. + */ + public void addRawOption(String option, Object value) + { + options.put(option, value); + } + public String toString() { StringBuffer buffer = new StringBuffer(); diff --git a/org.eclim/java/org/eclim/command/Options.java b/org.eclim/java/org/eclim/command/Options.java index 2ea50bce8..fe9cec3d8 100644 --- a/org.eclim/java/org/eclim/command/Options.java +++ b/org.eclim/java/org/eclim/command/Options.java @@ -62,6 +62,7 @@ public class Options public static final String FILE_OPTION = "f"; public static final String FOLDER_OPTION = "f"; public static final String FORCE_OPTION = "c"; // 'c' to avoid confusion in cleanup + public static final String CONTENT_OPTION = "c"; public static final String HELP = "help"; public static final String HALT_OPTION = "h"; public static final String HOST_OPTION = "h"; @@ -87,6 +88,7 @@ public class Options public static final String PROPERTIES_OPTION = "r"; public static final String REVISION_OPTION = "r"; public static final String ROOT_OPTION = "r"; + public static final String RECURSIVE_OPTION = "r"; public static final String SCHEMA_OPTION = "s"; public static final String SCOPE_OPTION = "s"; public static final String SEARCH_OPTION = "s"; diff --git a/org.eclim/java/org/eclim/eclipse/EclimDaemon.java b/org.eclim/java/org/eclim/eclipse/EclimDaemon.java index 1819508e5..539391de0 100644 --- a/org.eclim/java/org/eclim/eclipse/EclimDaemon.java +++ b/org.eclim/java/org/eclim/eclipse/EclimDaemon.java @@ -34,7 +34,7 @@ import org.eclim.Services; import org.eclim.command.ReloadCommand; - +import org.eclim.http.HTTPServer; import org.eclim.logging.Logger; import org.eclim.plugin.PluginResources; @@ -82,6 +82,8 @@ public class EclimDaemon private boolean stopping; private NGServer server; + private HTTPServer httpServer; + private EclimDaemon() { } @@ -117,6 +119,8 @@ public void start() server = new NGServer(address, port, getExtensionClassLoader()); server.setCaptureSystemStreams(false); + startHTTPServer(); + logger.info("Loading plugin org.eclim"); PluginResources defaultResources = Services.getPluginResources("org.eclim"); defaultResources.registerCommand(ReloadCommand.class); @@ -176,6 +180,26 @@ public void start() } } + private void startHTTPServer() + throws IOException + { + if (httpServerEnabled()){ + String httpServerPort = Services.getPluginResources(BASE) + .getProperty("http.server.port", "9998"); + String host = Services.getPluginResources(BASE) + .getProperty("http.server.host", "localhost"); + httpServer = new HTTPServer(); + httpServer.start(host, Integer.parseInt(httpServerPort)); + } + } + + private boolean httpServerEnabled() + { + String httpServerEnabled = Services.getPluginResources("org.eclim") + .getProperty("http.server.enabled"); + return "true".equals(httpServerEnabled); + } + public void stop() throws Exception { @@ -184,6 +208,10 @@ public void stop() stopping = true; logger.info("Shutting down eclim..."); + if (httpServer != null) { + httpServer.stop(); + } + if(server != null && server.isRunning()){ server.shutdown(false /* exit vm */); } diff --git a/org.eclim/java/org/eclim/http/CommandCaller.java b/org.eclim/java/org/eclim/http/CommandCaller.java new file mode 100644 index 000000000..b039edf0c --- /dev/null +++ b/org.eclim/java/org/eclim/http/CommandCaller.java @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.http; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclim.Services; +import org.eclim.command.Command; +import org.eclim.command.CommandException; +import org.eclim.command.CommandException.ErrorType; +import org.eclim.command.CommandLine; +import org.eclim.command.Options; +import org.eclim.logging.Logger; +import org.eclipse.swt.widgets.Display; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.martiansoftware.nailgun.NGContext; + +/** + * Class which replaces the {@code org.eclim.command.Main} class when commands + * are executed over the HTTP Interface. + * + * @author Lukas Roth + * + */ +public class CommandCaller implements Runnable +{ + private static final Logger logger = Logger.getLogger(CommandCaller.class); + + private final Command command; + private final Object[] results = new Object[1]; + private final CommandLine commandLine; + + public CommandCaller(Map parameters, + InputStream fileInputStream) + throws InvalidCommandException + { + try { + /** + * We want to add the {@code fileInputStream} to the command line. + * + * To do so we add a mock -c parameter which we will later override with + * the addRawOption method with the actual {@code fileInputStream}. + * + * If we would not add a (-c,"") pair before the parser gets called (in + * method createCommandLine) the parser would complain that the required + * option is not there. + */ + if(parameters.containsKey(Options.CONTENT_OPTION)){ + throw new InvalidCommandException(Services.getMessage( + "command.caller.file.post.not.allowed.key", Options.CONTENT_OPTION)); + } + parameters.put(Options.CONTENT_OPTION, ""); + commandLine = createCommandLine(parameters); + commandLine.addRawOption(Options.CONTENT_OPTION, fileInputStream); + command = commandLine.getCommand(); + logger.debug("created command {} from commandLine {}", command, commandLine); + } catch (Exception e) { + throw new InvalidCommandException( + "Could not create a command line out of " + parameters, e); + } + } + + public CommandCaller(Map parameters) + throws InvalidCommandException + { + try { + commandLine = createCommandLine(parameters); + command = commandLine.getCommand(); + logger.debug("created command {} from commandLine {}", command, commandLine); + } catch (Exception e) { + throw new InvalidCommandException( + "Could not create a command line out of " + parameters, e); + } + } + + public EclimHTTPResponse callCommand() + throws CommandCallerException + { + ByteArrayOutputStream outOutputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errOutputStream = new ByteArrayOutputStream(); + NGContext context = createMockContext(outOutputStream, errOutputStream); + + try { + command.setContext(context); + logger.debug("call command {}", command); + executeCommandsInsideEclipse(); + return parseResult(results[0], commandLine, outOutputStream, errOutputStream); + } catch (CommandCallerException e) { + logStreams(outOutputStream, errOutputStream); + throw e; + } + } + + private CommandLine createCommandLine(Map parameters) + throws Exception + { + List paramList = new ArrayList(); + for (Entry entry : parameters.entrySet()) { + // we add a '-' in front of every key such that the parser can parse our + // parameters + paramList.add("-" + entry.getKey()); + paramList.add(entry.getValue()); + } + + Options options = new Options(); + logger.debug("Creating a command line from arguments: " + + Arrays.toString(paramList.toArray())); + return options + .parse((String[]) paramList.toArray(new String[paramList.size()])); + } + + private void executeCommandsInsideEclipse() + { + Display.getDefault().syncExec(this); + } + + /** + * Logs the streams which were passed to into the Eclim plugin (inside the + * NGContext). The logging order may be wrong since we can log this + * information only in the end. + * + * @param outOutputStream + * @param errOutputStream + */ + private void logStreams(ByteArrayOutputStream outOutputStream, + ByteArrayOutputStream errOutputStream) + { + String out = outOutputStream.toString(); + if (!out.isEmpty()) { + logger.debug("Output stream passed to the commands:" + out); + } + String err = errOutputStream.toString(); + if (!err.isEmpty()) { + logger.debug("Error stream passed to the commands:" + err); + } + } + + private EclimHTTPResponse parseResult(Object result, CommandLine commandLine, + ByteArrayOutputStream outOutputStream, ByteArrayOutputStream errOutputStream) + throws CommandCallerException + { + if (result != null) { + String messageCommandException = "Command throwed an exception"; + if (result instanceof Exception) { + logger.error(messageCommandException, (Exception) result); + throw new CommandCallerException(messageCommandException, + (Exception) result); + } else if (result instanceof CommandException) { + CommandException commandException = (CommandException) result; + logger.error(messageCommandException, commandException); + int statusCode = getStatusCode(commandException); + String stringResult = (new Gson()).toJson(commandException); + return new EclimHTTPResponse(stringResult, outOutputStream.toString(), + errOutputStream.toString(), statusCode); + } + GsonBuilder builder = new GsonBuilder(); + if (commandLine.hasOption(Options.PRETTY_OPTION)) { + builder = builder.setPrettyPrinting(); + } + String stringResult = builder.create().toJson(result); + return new EclimHTTPResponse(stringResult, outOutputStream.toString(), + errOutputStream.toString(), 200); + } else { + String msg = "System error: The result of the call to eclim is null"; + logger.error(msg); + throw new CommandCallerException(msg); + } + } + + private int getStatusCode(CommandException commandException) + { + ErrorType errorType = commandException.getErrorType(); + if(errorType == ErrorType.SYSTEM_ERROR){ + return 500; + }else if(errorType == ErrorType.CLIENT_ERROR){ + return 400; + } + return 500; + } + + /** + * Create a "mock" NGcontext with out- and error streams set. + * + * @return NGContext + * @throws CommandCallerException + */ + private NGContext createMockContext(ByteArrayOutputStream outputStream, + ByteArrayOutputStream errStream) + throws RuntimeException + { + try { + // We use reflection to get the private constructor of the NGContext. + // We did not see a nicer way to get an ngContext instance. + Constructor constructor = NGContext.class + .getDeclaredConstructor(new Class[0]); + constructor.setAccessible(true); + NGContext ngContext = constructor.newInstance(new Object[0]); + ngContext.in = null; + ngContext.out = new PrintStream(outputStream); + ngContext.err = new PrintStream(errStream); + return ngContext; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + logger.error("Failed to construct a NGContext.", e); + throw new RuntimeException(e); + } + } + + @Override + public void run() + { + try { + results[0] = command.execute(commandLine); + } catch (Exception e) { + results[0] = e; + logger.error("Command failed", e); + } finally { + command.cleanup(commandLine); + } + } +} \ No newline at end of file diff --git a/org.eclim/java/org/eclim/http/CommandCallerException.java b/org.eclim/java/org/eclim/http/CommandCallerException.java new file mode 100644 index 000000000..d56316d81 --- /dev/null +++ b/org.eclim/java/org/eclim/http/CommandCallerException.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.http; + +import org.eclim.command.CommandException; + +/** + * Exception the CommandCaller throws. + * + * If the command has returned a {@code ReturnableException} we throw it inside + * this class. + * + * @author Lukas Roth + * + */ +public class CommandCallerException extends Exception +{ + private static final long serialVersionUID = 8692866089672811919L; + private final CommandException returnableException; + + public CommandCallerException(String message) + { + super(message); + this.returnableException = null; + } + + public CommandCallerException(String message, Exception cause) + { + super(message, cause); + this.returnableException = null; + } + + public CommandCallerException(CommandException returnableException) + { + this.returnableException = returnableException; + } + + public CommandException getReturnableException() + { + return returnableException; + } + + public boolean isReturnableException() + { + return returnableException != null; + } +} \ No newline at end of file diff --git a/org.eclim/java/org/eclim/http/EclimHTTPResponse.java b/org.eclim/java/org/eclim/http/EclimHTTPResponse.java new file mode 100644 index 000000000..dbc4cf3a2 --- /dev/null +++ b/org.eclim/java/org/eclim/http/EclimHTTPResponse.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.http; + +/** + * Describes how a HTTP response of eclim looks like. This object is returned + * (converted to JSON) in the response body. + * + * @author Lukas Roth + * + */ +public class EclimHTTPResponse +{ + private final String result; + private final String errStream; + private final String outStream; + private final int statusCode; + + public EclimHTTPResponse(String result, String outStream, String errStream, + int statusCode) + { + this.result = result; + this.outStream = outStream; + this.errStream = errStream; + this.statusCode = statusCode; + } + + public String getResult() + { + return result; + } + + public String getErrStream() + { + return errStream; + } + + public String getOutStream() + { + return outStream; + } + + public int getStatusCode() + { + return statusCode; + } +} diff --git a/org.eclim/java/org/eclim/http/HTTPServer.java b/org.eclim/java/org/eclim/http/HTTPServer.java new file mode 100644 index 000000000..41a3056d3 --- /dev/null +++ b/org.eclim/java/org/eclim/http/HTTPServer.java @@ -0,0 +1,275 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclim.Services; +import org.eclim.logging.Logger; + +import com.google.gson.Gson; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** + * The class {@code HTTPServer} provides the HTTP Interface of eclim. The class + * {@code HTTPServer} handles all requests which come over the HTTP Interface + * (and not over the NailGun interface). + * + * Look at the {@code org.eclim.EclimHTTPClient} inside the test cases for a + * reference on how to use the HTTP Interface of eclim. + * + * @author Lukas Roth + * + */ +public class HTTPServer implements HttpHandler +{ + private static final Logger logger = Logger.getLogger(HTTPServer.class); + private HttpServer server; + private boolean serverIsRunning = false; + + private static final String POST = "POST"; + private static final String GET = "GET"; + private static final String CONTENT_TYPE = "Content-type"; + private static final String CONTENT_TYPE_FORM = + "application/x-www-form-urlencoded"; + private static final String CONTENT_TYPE_APPLICATION_JSON = + "application/json"; + private static final String ENCODING = "UTF-8"; + + /** + * Starts the {@code HTTPServer} at the location specified in the arguments. + * + * @param hostname + * @param port + * @throws IOException + */ + public void start(String hostname, int port) + throws IOException + { + if (serverIsRunning) { + server.stop(0); + } + InetSocketAddress address = new InetSocketAddress(hostname, port); + server = HttpServer.create(address, 0); + server.createContext("/eclim/command", this); + server.setExecutor(null); // creates a default executor + server.start(); + serverIsRunning = true; + logger.info("Started HTTP server on: " + address.toString()); + } + + /** + * Stops the {@code HTTPServer} if he is running. If it is already stopped + * nothing is done. + */ + public void stop() + { + if (serverIsRunning && server != null) { + server.stop(0); + logger.info("Stopped HTTP server"); + } + } + + @Override + public void handle(HttpExchange httpExchange) + throws IOException + { + EclimHTTPResponse eclimResponse = null; + String requestMethod = httpExchange.getRequestMethod(); + try { + if (requestMethod.equals(POST)) { + eclimResponse = handlePost(httpExchange); + } else if (requestMethod.equals(GET)) { + eclimResponse = handleGet(httpExchange); + } else { + throw new HTTPServerException( + Services.getMessage("server.error.wrong.method"), 405); + } + } catch (HTTPServerException e) { + logger.error("Error while processing the http request (http status code: " + + e.getStatusCode() + ", error message: " + e.getMessage() + ")", e); + eclimResponse = createHTTPResponse(e); + } + + sendResponse(httpExchange, eclimResponse); + } + + private EclimHTTPResponse createHTTPResponse(HTTPServerException e) + throws IOException + { + String exceptionMessage = createExceptionMessage(e); + return new EclimHTTPResponse(exceptionMessage, "", "", e.getStatusCode()); + } + + private void sendResponse(HttpExchange httpExchange, + EclimHTTPResponse eclimResponse) + throws IOException + { + int statusCode = eclimResponse.getStatusCode(); + String body = (new Gson()).toJson(eclimResponse); + httpExchange.getResponseHeaders().add("Content-Type", + CONTENT_TYPE_APPLICATION_JSON); + httpExchange.sendResponseHeaders(statusCode, body.length()); + OutputStream os = httpExchange.getResponseBody(); + os.write(body.getBytes()); + os.close(); + } + + private String createExceptionMessage(HTTPServerException e) + { + return (new Gson()).toJson(new ExceptionResponse(e)); + } + + class ExceptionResponse + { + private final String exception; // NOSONAR used to generate JSON response + + public ExceptionResponse(Exception e) + { + StringWriter buf = new StringWriter(); + e.printStackTrace(new PrintWriter(buf)); + this.exception = buf.toString(); + } + } + + private EclimHTTPResponse handleGet(HttpExchange httpExchange) + throws HTTPServerException + { + return callCommand(parseParameters(httpExchange.getRequestURI().getQuery())); + } + + private EclimHTTPResponse handlePost(HttpExchange httpExchange) + throws HTTPServerException + { + Headers requestHeaders = httpExchange.getRequestHeaders(); + if (requestHeaders.containsKey(CONTENT_TYPE)) { + List contentType = requestHeaders.get(CONTENT_TYPE); + if (contentType.contains(CONTENT_TYPE_FORM)) { + return handlePostForm(httpExchange); + } else { + return handlePostFileUpload(httpExchange); + } + } else { + throw new HTTPServerException("server.error.content.type.not.set", 415); + } + } + + private EclimHTTPResponse handlePostFileUpload(HttpExchange httpExchange) + throws HTTPServerException + { + Map params = parseParameters( + httpExchange.getRequestURI().getQuery()); + InputStream fileInputStream = httpExchange.getRequestBody(); + return callCommand(params, fileInputStream); + } + + private EclimHTTPResponse handlePostForm(HttpExchange httpExchange) + throws HTTPServerException + { + InputStream inputStream = null; + try { + inputStream = httpExchange.getRequestBody(); + String inString = convertStreamToString(inputStream); + Map params = parseParameters(inString); + return callCommand(params); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + logger.warn("Could not close the request body input stream", e); + } + } + } + } + + private EclimHTTPResponse callCommand(Map parameters) + throws HTTPServerException + { + try { + CommandCaller eclimCaller = new CommandCaller(parameters); + return eclimCaller.callCommand(); + } catch (InvalidCommandException e) { + throw new HTTPServerException(e, 400); + } catch (Exception e) { + throw new HTTPServerException(e, 500); + } + } + + private EclimHTTPResponse callCommand(Map parameters, + InputStream fileInputStream) + throws HTTPServerException + { + try { + CommandCaller eclimCaller = new CommandCaller(parameters, fileInputStream); + return eclimCaller.callCommand(); + } catch (InvalidCommandException e) { + throw new HTTPServerException(e, 400); + } catch (Exception e) { + throw new HTTPServerException(e, 500); + } + } + + static String convertStreamToString(java.io.InputStream is) + { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + private static Map parseParameters(String parameters) + throws HTTPServerException + { + try { + final Map res = new HashMap(); + final String[] pairs = parameters.split("&"); + for (String pair : pairs) { + final int idx = pair.indexOf('='); + final String key = idx > 0 ? + URLDecoder.decode(pair.substring(0, idx), ENCODING) : pair; + if (res.containsKey(key)) { + throw new HTTPServerException( + Services.getMessage("server.error.argument.key.not.unique"), 400); + } + final String value = idx > 0 && pair.length() > idx + 1 ? + URLDecoder.decode(pair.substring(idx + 1), ENCODING) : null; + if (value != null) { + res.put(key, value); + } else { + res.put(key, ""); + } + } + return res; + } catch (UnsupportedEncodingException e) { + String errMsg = Services.getMessage("server.error.argument.decoding"); + logger.error(errMsg, e); + throw new HTTPServerException(errMsg, 400); + } + } +} \ No newline at end of file diff --git a/org.eclim/java/org/eclim/http/HTTPServerException.java b/org.eclim/java/org/eclim/http/HTTPServerException.java new file mode 100644 index 000000000..036db590d --- /dev/null +++ b/org.eclim/java/org/eclim/http/HTTPServerException.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2005 - 2016 Eric Van Dewoestine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.eclim.http; + +/** + * Exception thrown by the {@codeHTTPServer}. + * + * @author Lukas Roth + * + */ +public class HTTPServerException extends Exception +{ + private static final long serialVersionUID = -3627071763625417908L; + private final int statusCode; + + public HTTPServerException(Exception e, int statusCode) + { + super(e); + this.statusCode = statusCode; + } + + public HTTPServerException(String message, int statusCode) + { + super(message); + this.statusCode = statusCode; + } + + public int getStatusCode() + { + return statusCode; + } +} diff --git a/org.eclim/java/org/eclim/http/InvalidCommandException.java b/org.eclim/java/org/eclim/http/InvalidCommandException.java new file mode 100644 index 000000000..7e0e472d0 --- /dev/null +++ b/org.eclim/java/org/eclim/http/InvalidCommandException.java @@ -0,0 +1,15 @@ +package org.eclim.http; + +public class InvalidCommandException extends CommandCallerException +{ + + public InvalidCommandException(String message, Exception cause) + { + super(message, cause); + } + + public InvalidCommandException(String message) + { + super(message); + } +} diff --git a/org.eclim/java/org/eclim/logging/Logger.java b/org.eclim/java/org/eclim/logging/Logger.java index 46958d8e9..287fffac2 100644 --- a/org.eclim/java/org/eclim/logging/Logger.java +++ b/org.eclim/java/org/eclim/logging/Logger.java @@ -25,12 +25,8 @@ */ public class Logger { - private static String workspace = ResourcesPlugin - .getWorkspace().getRoot().getRawLocation().toOSString().replace('\\', '/'); - static{ - // set on class load so that the logger can log to: - // ${eclimd.workspace}/eclimd.log - System.setProperty("eclimd.workspace", workspace); + static { + initWorkspace(); } private org.slf4j.Logger logger; @@ -40,6 +36,30 @@ private Logger (org.slf4j.Logger logger) this.logger = logger; } + /** + * set on class load so that the logger can log to: + * ${eclimd.workspace}/eclimd.log + * + * If we unit test the code we run the logger not in an eclipse environment + * --> the eclimd.workspace system property is already set from the running + * test-eclimd instance. We catch the exception which will be thrown here + * (Since ResourcesPlugin is not in the classpath). + * + * We could also have checked if the eclimd.workspace property is set and only + * set it if it is not set, but this would be a change in behavior --> we + * decided to catch the exception. + */ + private static void initWorkspace() + { + try { + String workspace = ResourcesPlugin.getWorkspace().getRoot().getRawLocation() + .toOSString().replace('\\', '/'); + System.setProperty("eclimd.workspace", workspace); + } catch (NoClassDefFoundError t) { + t = null; // ignore: means we are not running within eclipse + } + } + /** * Gets the logger for the specified class. * diff --git a/org.eclim/java/org/eclim/messages.properties b/org.eclim/java/org/eclim/messages.properties index 3b424806c..5c114b972 100644 --- a/org.eclim/java/org/eclim/messages.properties +++ b/org.eclim/java/org/eclim/messages.properties @@ -55,3 +55,11 @@ http.error=Http Error: {0} command.description=The command to execute. pretty.description=Output json response is more human readable format. editor.description=The editor invoking the command. + +# HTTP server messages +server.error.wrong.method=We only support GET and POST requests. +server.error.content.type.not.set=Content type not set. +server.error.argument.key.not.unique=Each argument needs an unique key. +server.error.argument.decoding=Can not decode the arguments. +server.error.system=System error. +command.caller.file.post.not.allowed.key=The ''{0}'' key is not allowed in a command which passes a file in the body of the request. \ No newline at end of file diff --git a/org.eclim/plugin.properties b/org.eclim/plugin.properties index 796c5259f..96c47fa69 100644 --- a/org.eclim/plugin.properties +++ b/org.eclim/plugin.properties @@ -1,4 +1,7 @@ pluginVersion=${eclim.version} nailgun.server.host=127.0.0.1 nailgun.server.port=9091 +http.server.enabled=true +http.server.port=9998 +http.server.host=localhost vim.files=${vim.files} From 10799305c01b17dba3e9694c4f8764c891cb7de5 Mon Sep 17 00:00:00 2001 From: Lukas Roth Date: Tue, 3 Jan 2017 10:28:47 +0100 Subject: [PATCH 2/4] =?UTF-8?q?HTTP=20Interface=20#495=20=E2=80=93=20corre?= =?UTF-8?q?ctions=20resulting=20of=20the=20'first=20review=20round'=20of?= =?UTF-8?q?=20ervandew?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.gant: use ${pluginName} instead of hard coding - use IOUtils in FileSaveCommand - move org.eclim.plugin.jdt.command.dependency to jdt.command.classpath - disable http server by default - rename add_dependency and remove_dependency commands to java_classpath_add_entry and java_classpath_remove_entry --- ant/build.gant | 4 ++-- .../plugin/core/command/file/FileListCommand.java | 4 ++-- .../plugin/core/command/file/FileSaveCommand.java | 11 ++--------- .../ClasspathAddEntryCommand.java} | 8 ++++---- .../ClasspathFileManipulator.java | 2 +- .../ClasspathFileManipulatorException.java | 2 +- .../ClasspathRemoveEntryCommand.java} | 8 ++++---- .../SimpleClasspathFileManipulator.java | 2 +- .../ClasspathAddRemoveEntryCommandTest.java} | 8 ++++---- .../SimpleClasspathFileManipulatorTest.java | 6 +++--- .../testfiles/OriginalClasspath.txt | 0 .../{dependency => classpath}/testfiles/add1.txt | 0 .../testfiles/add1_expected.txt | 0 .../testfiles/addRemove1.txt | 0 .../testfiles/addRemove1_expected.txt | 0 .../testfiles/addRemoveMultipleTimes1.txt | 0 .../testfiles/addRemoveMultipleTimes1_expected.txt | 0 .../testfiles/addTwoTimesSame1.txt | 0 .../testfiles/addTwoTimesSame1_expected.txt | 0 .../{dependency => classpath}/testfiles/remove1.txt | 0 .../testfiles/remove1_expected.txt | 0 .../testfiles/removeNotExisting1.txt | 0 .../testfiles/removeNotExisting1_expected.txt | 0 org.eclim/plugin.properties | 2 +- 24 files changed, 25 insertions(+), 32 deletions(-) rename org.eclim.jdt/java/org/eclim/plugin/jdt/command/{dependency/AddDependencyCommand.java => classpath/ClasspathAddEntryCommand.java} (94%) rename org.eclim.jdt/java/org/eclim/plugin/jdt/command/{dependency => classpath}/ClasspathFileManipulator.java (96%) rename org.eclim.jdt/java/org/eclim/plugin/jdt/command/{dependency => classpath}/ClasspathFileManipulatorException.java (95%) rename org.eclim.jdt/java/org/eclim/plugin/jdt/command/{dependency/RemoveDependencyCommand.java => classpath/ClasspathRemoveEntryCommand.java} (94%) rename org.eclim.jdt/java/org/eclim/plugin/jdt/command/{dependency => classpath}/SimpleClasspathFileManipulator.java (99%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency/JarUploadRemoveCommandTest.java => classpath/ClasspathAddRemoveEntryCommandTest.java} (96%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/SimpleClasspathFileManipulatorTest.java (97%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/OriginalClasspath.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/add1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/add1_expected.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addRemove1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addRemove1_expected.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addRemoveMultipleTimes1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addRemoveMultipleTimes1_expected.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addTwoTimesSame1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/addTwoTimesSame1_expected.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/remove1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/remove1_expected.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/removeNotExisting1.txt (100%) rename org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/{dependency => classpath}/testfiles/removeNotExisting1_expected.txt (100%) diff --git a/ant/build.gant b/ant/build.gant index 075abf2e4..c3c91884a 100644 --- a/ant/build.gant +++ b/ant/build.gant @@ -1115,7 +1115,7 @@ junit = { pluginName -> ant.include(name: '**/*.java') ant.classpath{ ant.pathelement(path: '${build.classes}/org.eclim') - ant.pathelement(path: '${build.classes}/org.eclim.jdt') + ant.pathelement(path: '${build.classes}' + "/${pluginName}") } } @@ -1133,7 +1133,7 @@ junit = { pluginName -> ant.path(refid: 'junit') ant.pathelement(path: "build/test/junit/classes/${pluginName}") ant.pathelement(path: '${build.classes}/org.eclim') - ant.pathelement(path: '${build.classes}/org.eclim.jdt') + ant.pathelement(path: '${build.classes}' + "/${pluginName}") ant.fileset(dir: 'org.eclim/lib', includes: '*.jar', excludes: 'ant-*.jar') } ant.formatter(type: 'xml') diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java index 010cf6e22..5eca6733b 100644 --- a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileListCommand.java @@ -44,7 +44,7 @@ * {@code project}. * * Example response: - * + * * myFolder/ * mySecFolder/ * myFile.txt @@ -55,7 +55,7 @@ * all the folders inclusive all subfolders and subfiles. * * Example response with {@code recursiveFlag} set to true: - * + * * myFolder/ * myFolder/mySubFolder/ * myFolder/mySubFolder/mySubFile.txt diff --git a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java index fa0541e81..b818ea8b5 100644 --- a/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java +++ b/org.eclim.core/java/org/eclim/plugin/core/command/file/FileSaveCommand.java @@ -34,6 +34,7 @@ import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.core.util.PathUtil; import org.eclim.plugin.core.util.PathUtilException; +import org.eclim.util.IOUtils; @Command( name = "file_save", @@ -124,18 +125,10 @@ private void writeToFileSystem(String absoluteFilePath, InputStream fileContent) copyInputStreamToFile(fileContent, file); } - // copied from - // http://stackoverflow.com/questions/43157/easy-way-to-write-contents-of-a-java-inputstream-to-an-outputstream private void copyInputStreamToFile(InputStream in, File file) throws IOException { OutputStream out = new FileOutputStream(file); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - out.close(); - in.close(); + IOUtils.copy(in, out); } } diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathAddEntryCommand.java similarity index 94% rename from org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java rename to org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathAddEntryCommand.java index 59a87b2e4..34e5dd5da 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/AddDependencyCommand.java +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathAddEntryCommand.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; import org.eclim.Services; import org.eclim.annotation.Command; @@ -28,7 +28,7 @@ import org.eclim.plugin.core.util.PathUtilException; @Command( - name = "add_dependency", + name = "java_classpath_add_entry", options = "REQUIRED p project ARG," + "REQUIRED f relativeFilePath ARG" @@ -48,10 +48,10 @@ * @author Lukas Roth * */ -public class AddDependencyCommand extends AbstractCommand +public class ClasspathAddEntryCommand extends AbstractCommand { private static final Logger logger = Logger - .getLogger(AddDependencyCommand.class); + .getLogger(ClasspathAddEntryCommand.class); @Override public Object execute(CommandLine commandLine) diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulator.java similarity index 96% rename from org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java rename to org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulator.java index b8bd40cdc..2cc42bfb3 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulator.java +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulator.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; /** * The {@code ClasspathFileManipulator} Interface should be implemented by all diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulatorException.java similarity index 95% rename from org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java rename to org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulatorException.java index 4cb23be91..a768bffdb 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/ClasspathFileManipulatorException.java +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathFileManipulatorException.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; /** * Exception thrown by the {@code ClasspathFileManipulator}. diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathRemoveEntryCommand.java similarity index 94% rename from org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java rename to org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathRemoveEntryCommand.java index f70f04953..a42f1824c 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/RemoveDependencyCommand.java +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/ClasspathRemoveEntryCommand.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; import org.eclim.Services; import org.eclim.annotation.Command; @@ -28,7 +28,7 @@ import org.eclim.plugin.core.util.PathUtilException; @Command( - name = "remove_dependency", + name = "java_classpath_remove_entry", options = "REQUIRED p project ARG," + "REQUIRED f relativeFilePath ARG" @@ -48,10 +48,10 @@ * @author Lukas Roth * */ -public class RemoveDependencyCommand extends AbstractCommand +public class ClasspathRemoveEntryCommand extends AbstractCommand { private static final Logger logger = Logger - .getLogger(RemoveDependencyCommand.class); + .getLogger(ClasspathRemoveEntryCommand.class); @Override public Object execute(CommandLine commandLine) diff --git a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulator.java similarity index 99% rename from org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java rename to org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulator.java index d31f4c1fb..ac5f68a29 100644 --- a/org.eclim.jdt/java/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulator.java +++ b/org.eclim.jdt/java/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulator.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; import java.io.File; import java.util.ArrayList; diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/ClasspathAddRemoveEntryCommandTest.java similarity index 96% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/ClasspathAddRemoveEntryCommandTest.java index 2d845317b..c1f6e485f 100644 --- a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/JarUploadRemoveCommandTest.java +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/ClasspathAddRemoveEntryCommandTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; import static org.junit.Assert.assertTrue; @@ -38,7 +38,7 @@ import junit.framework.Assert; /** - * This test we upload a dependency jar, manipulate the classpat file such that + * In this test we upload a dependency jar, manipulate the classpat file such that * eclipse knows we uploaded a jar and then remove the dependency jar again. * * Test idea: Upload and remove a GSON Library and check that - before the @@ -51,7 +51,7 @@ * * @author Lukas Roth */ -public class JarUploadRemoveCommandTest +public class ClasspathAddRemoveEntryCommandTest { private static final String TEST_FILE = "src/org/eclim/test/exampleClass.java"; private static final String TEST_FILE_CONTENT = "package org.eclim.test;\nclass A{\nvoid f(){\nGsonB}\n}"; @@ -160,7 +160,7 @@ private void addToClasspath() throws IOException { Map uploadJarParams = new HashMap(); - uploadJarParams.put("command", "add_dependency"); + uploadJarParams.put("command", "java_classpath_add_entry"); uploadJarParams.put("p", Jdt.TEST_PROJECT); uploadJarParams.put("f", PATH_TO_DEPENDENCY); eclimHTTPClient.post(uploadJarParams); diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulatorTest.java similarity index 97% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulatorTest.java index 74a8863e1..d8eb8701f 100644 --- a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/SimpleClasspathFileManipulatorTest.java +++ b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/SimpleClasspathFileManipulatorTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.eclim.plugin.jdt.command.dependency; +package org.eclim.plugin.jdt.command.classpath; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -38,13 +38,13 @@ */ public class SimpleClasspathFileManipulatorTest { - private static final String TEST_FILES_BASE_FOLDER = "org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/"; + private static final String TEST_FILES_BASE_FOLDER = "org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/"; private static List cleanupList = new ArrayList(); private String dependencyLocation1 = "/example/folder/bla.jar"; private String dependencyLocation2 = "/example/folder/bla2.jar"; private String dependencyLocation3 = "/example/folder/bla3.jar"; private String dependencyLocation4 = "/example/folder/bla4.jar"; - ClasspathFileManipulator classpathFileManipulator = new SimpleClasspathFileManipulator();; + ClasspathFileManipulator classpathFileManipulator = new SimpleClasspathFileManipulator(); @Test public void normalAddJarDependencyToClasspath() diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/OriginalClasspath.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/OriginalClasspath.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/OriginalClasspath.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/add1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/add1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/add1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/add1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/add1_expected.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemove1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemove1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemove1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemove1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemove1_expected.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemoveMultipleTimes1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemoveMultipleTimes1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemoveMultipleTimes1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addRemoveMultipleTimes1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addRemoveMultipleTimes1_expected.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addTwoTimesSame1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addTwoTimesSame1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addTwoTimesSame1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/addTwoTimesSame1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/addTwoTimesSame1_expected.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/remove1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/remove1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/remove1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/remove1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/remove1_expected.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/removeNotExisting1.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/removeNotExisting1.txt diff --git a/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt b/org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/removeNotExisting1_expected.txt similarity index 100% rename from org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/dependency/testfiles/removeNotExisting1_expected.txt rename to org.eclim.jdt/test/junit/org/eclim/plugin/jdt/command/classpath/testfiles/removeNotExisting1_expected.txt diff --git a/org.eclim/plugin.properties b/org.eclim/plugin.properties index 96c47fa69..577292957 100644 --- a/org.eclim/plugin.properties +++ b/org.eclim/plugin.properties @@ -1,7 +1,7 @@ pluginVersion=${eclim.version} nailgun.server.host=127.0.0.1 nailgun.server.port=9091 -http.server.enabled=true +http.server.enabled=false http.server.port=9998 http.server.host=localhost vim.files=${vim.files} From 4185c48c1cadaea4f84ed4a178d5e1318b703627 Mon Sep 17 00:00:00 2001 From: Lukas Roth Date: Wed, 4 Jan 2017 17:00:48 +0100 Subject: [PATCH 3/4] HTTP Interface #495: small bug in logging message - changed wrong logging call (tried to log an object which is no instance of exception in an error logging call) --- org.eclim/java/org/eclim/http/CommandCaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclim/java/org/eclim/http/CommandCaller.java b/org.eclim/java/org/eclim/http/CommandCaller.java index b039edf0c..b4f4b7609 100644 --- a/org.eclim/java/org/eclim/http/CommandCaller.java +++ b/org.eclim/java/org/eclim/http/CommandCaller.java @@ -171,9 +171,9 @@ private EclimHTTPResponse parseResult(Object result, CommandLine commandLine, (Exception) result); } else if (result instanceof CommandException) { CommandException commandException = (CommandException) result; - logger.error(messageCommandException, commandException); int statusCode = getStatusCode(commandException); String stringResult = (new Gson()).toJson(commandException); + logger.error(messageCommandException, stringResult); return new EclimHTTPResponse(stringResult, outOutputStream.toString(), errOutputStream.toString(), statusCode); } From 2cef94e6e0f5be364a85892da3aa1f7231c7bb47 Mon Sep 17 00:00:00 2001 From: Lukas Roth Date: Fri, 6 Jan 2017 10:27:24 +0100 Subject: [PATCH 4/4] HTTP Interface #495 - Allow commands to return null The HTTP Server returned an error message if a command returns null. Since there exist commands which return null when they succeed (e.g. project_refresh_file) we changed this. The HTTP Server now returns an empty string in the message if a command returns null. --- .../java/org/eclim/http/CommandCaller.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/org.eclim/java/org/eclim/http/CommandCaller.java b/org.eclim/java/org/eclim/http/CommandCaller.java index b4f4b7609..f41977a23 100644 --- a/org.eclim/java/org/eclim/http/CommandCaller.java +++ b/org.eclim/java/org/eclim/http/CommandCaller.java @@ -163,32 +163,32 @@ private EclimHTTPResponse parseResult(Object result, CommandLine commandLine, ByteArrayOutputStream outOutputStream, ByteArrayOutputStream errOutputStream) throws CommandCallerException { - if (result != null) { - String messageCommandException = "Command throwed an exception"; - if (result instanceof Exception) { - logger.error(messageCommandException, (Exception) result); - throw new CommandCallerException(messageCommandException, - (Exception) result); - } else if (result instanceof CommandException) { - CommandException commandException = (CommandException) result; - int statusCode = getStatusCode(commandException); - String stringResult = (new Gson()).toJson(commandException); - logger.error(messageCommandException, stringResult); - return new EclimHTTPResponse(stringResult, outOutputStream.toString(), - errOutputStream.toString(), statusCode); - } - GsonBuilder builder = new GsonBuilder(); - if (commandLine.hasOption(Options.PRETTY_OPTION)) { - builder = builder.setPrettyPrinting(); - } - String stringResult = builder.create().toJson(result); + if(result == null){ + // There exist commands (e.g project_refresh) + // which return null even if they succeed + // -> we return an empty string if a command returns null + result = ""; + } + String messageCommandException = "Command throwed an exception"; + if (result instanceof Exception) { + logger.error(messageCommandException, (Exception) result); + throw new CommandCallerException(messageCommandException, + (Exception) result); + } else if (result instanceof CommandException) { + CommandException commandException = (CommandException) result; + int statusCode = getStatusCode(commandException); + String stringResult = (new Gson()).toJson(commandException); + logger.error(messageCommandException, stringResult); return new EclimHTTPResponse(stringResult, outOutputStream.toString(), - errOutputStream.toString(), 200); - } else { - String msg = "System error: The result of the call to eclim is null"; - logger.error(msg); - throw new CommandCallerException(msg); + errOutputStream.toString(), statusCode); + } + GsonBuilder builder = new GsonBuilder(); + if (commandLine.hasOption(Options.PRETTY_OPTION)) { + builder = builder.setPrettyPrinting(); } + String stringResult = builder.create().toJson(result); + return new EclimHTTPResponse(stringResult, outOutputStream.toString(), + errOutputStream.toString(), 200); } private int getStatusCode(CommandException commandException)