From 73228618749955e3c54045c3040caa0fe2ee07c0 Mon Sep 17 00:00:00 2001 From: Florent BENOIT Date: Thu, 22 Oct 2015 11:42:00 +0200 Subject: [PATCH] UD-806 Allow to drag and drop local files to the Che instance Change-Id: I18d33c9f9abb3ee5c70504a14e36a39a1056a224 Signed-off-by: Florent BENOIT --- .../che/admin/deploy/ApiServletModule.java | 2 + .../eclipse/che/admin/deploy/Constants.java | 23 ++++++ .../che/admin/upload/UploadServlet.java | 79 ++++++++++++++++++ .../src/main/webapp/META-INF/context.xml | 14 ++++ .../webapp/WEB-INF/classes/che/che.properties | 13 +++ .../che/plugin/CustomExceptionMapper.java | 23 +++++- .../eclipse/che/plugin/PluginGuiceModule.java | 3 + .../plugin/internal/api/PluginManager.java | 2 +- .../api/PluginResolverNotFoundException.java | 18 ++++- .../internal/manager/PluginManagerImpl.java | 2 +- .../repository/PluginRepositoryImpl.java | 10 +-- .../resolver/LocalUploadResolver.java | 80 +++++++++++++++++++ 12 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 che-admin-war/src/main/java/org/eclipse/che/admin/deploy/Constants.java create mode 100644 che-admin-war/src/main/java/org/eclipse/che/admin/upload/UploadServlet.java create mode 100644 che-admin-war/src/main/webapp/META-INF/context.xml create mode 100644 che-admin-war/src/main/webapp/WEB-INF/classes/che/che.properties create mode 100644 plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/resolver/LocalUploadResolver.java diff --git a/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/ApiServletModule.java b/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/ApiServletModule.java index 62bc7eb90b3..bb03ea1f959 100644 --- a/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/ApiServletModule.java +++ b/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/ApiServletModule.java @@ -12,6 +12,7 @@ import com.google.inject.servlet.ServletModule; +import org.eclipse.che.admin.upload.UploadServlet; import org.eclipse.che.inject.DynaModule; import org.everrest.guice.servlet.GuiceEverrestServlet; @@ -23,6 +24,7 @@ public class ApiServletModule extends ServletModule { @Override protected void configureServlets() { + serve("/upload").with(UploadServlet.class); serve("/*", new String[0]).with(GuiceEverrestServlet.class); } } diff --git a/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/Constants.java b/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/Constants.java new file mode 100644 index 00000000000..4a90ce8ba0d --- /dev/null +++ b/che-admin-war/src/main/java/org/eclipse/che/admin/deploy/Constants.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2012-2015 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.admin.deploy; + +/** + * Constants that might be defined in che.properties file. + * @author Florent Benoit + */ +public class Constants { + + /** + * Constant used to describe the path to the servlet upload directory when uploading files. + */ + public static final String CHE_SERVLET_UPLOAD_DIRECTORY = "che.servlet.upload.directory"; +} diff --git a/che-admin-war/src/main/java/org/eclipse/che/admin/upload/UploadServlet.java b/che-admin-war/src/main/java/org/eclipse/che/admin/upload/UploadServlet.java new file mode 100644 index 00000000000..0707dfceee5 --- /dev/null +++ b/che-admin-war/src/main/java/org/eclipse/che/admin/upload/UploadServlet.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2012-2015 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.admin.upload; + +import org.eclipse.che.admin.deploy.Constants; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Upload servlet allowing to upload files to the server. Request should contain a part named 'uploadedFile' for the file. + * @author Florent Benoit + */ +@MultipartConfig +@Singleton +public class UploadServlet extends HttpServlet { + + /** + * Serializable class; + */ + private static final long serialVersionUID = -687991492884005033L; + + /** + * Folder used to store uploaded files. + */ + private String uploadFolder; + + /** + * Create a new upload folder. + * @param uploadFolder the path to store the uploaded files. + */ + @Inject + public UploadServlet(@Named(Constants.CHE_SERVLET_UPLOAD_DIRECTORY) String uploadFolder) { + this.uploadFolder = uploadFolder; + } + + /** + * Handle the POST request only by allowing to upload files. + * @param request the request with a part named 'uploadedFile' + * @param response empty if file is uploaded + * @throws ServletException if there is a problem while uploading + * @throws IOException if there is a problem while uploading + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getParts().stream().forEach((filePart -> { + try { + String fileName = filePart.getSubmittedFileName(); + InputStream inputStream = filePart.getInputStream(); + Path path = Paths.get(uploadFolder).resolve(fileName); + Files.createDirectories(path.getParent()); + Files.copy(inputStream, path); + + } catch (IOException e) { + throw new RuntimeException("Unable to upload files", e); + } + })); + } + + +} \ No newline at end of file diff --git a/che-admin-war/src/main/webapp/META-INF/context.xml b/che-admin-war/src/main/webapp/META-INF/context.xml new file mode 100644 index 00000000000..8f920023675 --- /dev/null +++ b/che-admin-war/src/main/webapp/META-INF/context.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/che-admin-war/src/main/webapp/WEB-INF/classes/che/che.properties b/che-admin-war/src/main/webapp/WEB-INF/classes/che/che.properties new file mode 100644 index 00000000000..aaf84a97cf2 --- /dev/null +++ b/che-admin-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -0,0 +1,13 @@ +# +# Copyright (c) 2012-2015 Codenvy, S.A. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Codenvy, S.A. - initial API and implementation +# + +# Directory used to upload files +che.servlet.upload.directory=${catalina.base}/temp/upload diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/CustomExceptionMapper.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/CustomExceptionMapper.java index ec57ba79a5f..a6ef5cd40dd 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/CustomExceptionMapper.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/CustomExceptionMapper.java @@ -34,24 +34,41 @@ public class CustomExceptionMapper implements ExceptionMapper { @Override public Response toResponse(PluginException exception) { + // try to get the inner cause + Throwable innerException = getInnerCause(exception); + String message = exception.getMessage() + ": " + innerException.getClass().getSimpleName() + " " + innerException.getMessage(); + if (exception instanceof PluginManagerNotFoundException || exception instanceof PluginInstallerNotFoundException || exception instanceof PluginResolverNotFoundException) { return Response.status(Response.Status.NOT_FOUND) .entity(DtoFactory.getInstance() .toJson(DtoFactory.getInstance().createDto(ServiceError.class) - .withMessage(exception.getMessage()))) + .withMessage(message))) .type(MediaType.APPLICATION_JSON) .build(); } else if (exception instanceof PluginManagerAlreadyExistsException) { return Response.status(Response.Status.CONFLICT) .entity(DtoFactory.getInstance() .toJson(DtoFactory.getInstance().createDto(ServiceError.class) - .withMessage(exception.getMessage()))) + .withMessage(message))) .type(MediaType.APPLICATION_JSON) .build(); } return Response.serverError() - .entity(DtoFactory.getInstance().createDto(ServiceError.class).withMessage(exception.getMessage())) + .entity(DtoFactory.getInstance().createDto(ServiceError.class).withMessage(message)) .type(MediaType.APPLICATION_JSON) .build(); } + + /** + * Gets the inner cause or return current exception if there is no root cause + * @param throwable iterate over this throwable + * @return current or the deepest inner cause + */ + protected Throwable getInnerCause(Throwable throwable) { + if (throwable.getCause() == null) { + return throwable; + } + return getInnerCause(throwable.getCause()); + } + } \ No newline at end of file diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/PluginGuiceModule.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/PluginGuiceModule.java index 880ac7052e9..ce80c07f2e0 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/PluginGuiceModule.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/PluginGuiceModule.java @@ -23,6 +23,7 @@ import org.eclipse.che.plugin.internal.installer.PluginInstallerImpl; import org.eclipse.che.plugin.internal.manager.PluginManagerImpl; import org.eclipse.che.plugin.internal.repository.PluginRepositoryImpl; +import org.eclipse.che.plugin.internal.resolver.LocalUploadResolver; import org.eclipse.che.plugin.internal.resolver.MavenResolver; /** @@ -44,6 +45,8 @@ protected void configure() { Multibinder pluginDownloaderMultibinder = Multibinder.newSetBinder(binder(), PluginResolver.class); pluginDownloaderMultibinder.addBinding().to(MavenResolver.class); + pluginDownloaderMultibinder.addBinding().to(LocalUploadResolver.class); + } } diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginManager.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginManager.java index 7fc25fe137f..ce758ee9985 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginManager.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginManager.java @@ -22,7 +22,7 @@ public interface PluginManager { */ List listPlugins() throws PluginRepositoryException; - IPlugin addPlugin(String mavenPluginRef) throws PluginManagerException, PluginResolverNotFoundException; + IPlugin addPlugin(String mavenPluginRef) throws PluginManagerException, PluginResolverNotFoundException, PluginResolverNotFoundException; IPlugin getPluginDetails(String pluginName) throws PluginManagerNotFoundException; diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginResolverNotFoundException.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginResolverNotFoundException.java index d10bba5e46f..21e6bad25d0 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginResolverNotFoundException.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/api/PluginResolverNotFoundException.java @@ -11,12 +11,26 @@ package org.eclipse.che.plugin.internal.api; /** + * Exception if plugin is not found during the resolution of the url * @author Florent Benoit */ public class PluginResolverNotFoundException extends PluginException { - public PluginResolverNotFoundException(String s, Throwable e) { - super(s, e); + /** + * Create exception with a given message. + * @param message the error message + */ + public PluginResolverNotFoundException(String message) { + super(message); + } + + /** + * Create exception with a given message and a root cause. + * @param message the error message + * @param e the root cause + */ + public PluginResolverNotFoundException(String message, Throwable e) { + super(message, e); } } diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/manager/PluginManagerImpl.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/manager/PluginManagerImpl.java index 817027b9dcf..36cca81b24e 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/manager/PluginManagerImpl.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/manager/PluginManagerImpl.java @@ -251,7 +251,7 @@ public void onFailure(Throwable throwable) { } - public IPlugin addPlugin(final String pluginRef) throws PluginResolverNotFoundException, PluginManagerException { + public IPlugin addPlugin(final String pluginRef) throws PluginResolverNotFoundException, PluginManagerException, PluginResolverNotFoundException { // resolve plugin diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/repository/PluginRepositoryImpl.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/repository/PluginRepositoryImpl.java index e45db161d58..387e98d6802 100644 --- a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/repository/PluginRepositoryImpl.java +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/repository/PluginRepositoryImpl.java @@ -226,7 +226,7 @@ public Path add(Path localPlugin) throws PluginRepositoryException { try { Files.move(localPlugin, destPath); } catch (IOException e) { - throw new PluginRepositoryException("Unable to move plugin to staged directory"); + throw new PluginRepositoryException("Unable to move plugin to staged directory", e); } return destPath; @@ -249,7 +249,7 @@ public Path stageInstall(Path availablePlugin) throws PluginRepositoryException try { Files.move(availablePlugin, newLocation); } catch (IOException e) { - throw new PluginRepositoryException("Unable to move plugin to staged directory"); + throw new PluginRepositoryException("Unable to move plugin to staged directory", e); } @@ -273,7 +273,7 @@ public Path undoStageInstall(Path stagedInstallPlugin) throws PluginRepositoryEx try { Files.move(stagedInstallPlugin, newLocation); } catch (IOException e) { - throw new PluginRepositoryException("Unable to move plugin to staged directory"); + throw new PluginRepositoryException("Unable to move plugin to staged directory", e); } @@ -296,7 +296,7 @@ public Path undoStageUninstall(Path stagedUninstallPlugin) throws PluginReposito try { Files.move(stagedUninstallPlugin, newLocation); } catch (IOException e) { - throw new PluginRepositoryException("Unable to move plugin to staged directory"); + throw new PluginRepositoryException("Unable to move plugin to staged directory", e); } @@ -320,7 +320,7 @@ public Path stageUninstall(Path installedPlugin) throws PluginRepositoryExceptio try { Files.move(installedPlugin, newLocation); } catch (IOException e) { - throw new PluginRepositoryException("Unable to move plugin to available directory"); + throw new PluginRepositoryException("Unable to move plugin to available directory", e); } diff --git a/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/resolver/LocalUploadResolver.java b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/resolver/LocalUploadResolver.java new file mode 100644 index 00000000000..7c4ed63c6cd --- /dev/null +++ b/plugin-tools/che-api-plugin/src/main/java/org/eclipse/che/plugin/internal/resolver/LocalUploadResolver.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2012-2015 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.plugin.internal.resolver; + +import org.eclipse.che.plugin.internal.api.PluginResolver; +import org.eclipse.che.plugin.internal.api.PluginResolverException; +import org.eclipse.che.plugin.internal.api.PluginResolverNotFoundException; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.validation.constraints.NotNull; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * LocalUpload Resolve will resolve jars that have been uploaded to the che instance. + * @author Florent Benoit + */ +@Singleton +public class LocalUploadResolver implements PluginResolver { + + /** + * Constant used to describe the path to the servlet upload directory when uploading files. + */ + public static final String CHE_SERVLET_UPLOAD_DIRECTORY = "che.servlet.upload.directory"; + + /** + * Folder used to store uploaded files. + */ + private Path uploadFolder; + + /** + * Create a new resolver using the local upload folder. + * @param uploadFolder the path to store the uploaded files. + */ + @Inject + public LocalUploadResolver(@Named(CHE_SERVLET_UPLOAD_DIRECTORY) String uploadFolder) { + this.uploadFolder = Paths.get(uploadFolder); + } + + /** + * Resolve provided url + * @param pluginRef reference of the plugin + * @return + * @throws PluginResolverException + */ + public Path download(@NotNull final String pluginRef) throws PluginResolverException, PluginResolverNotFoundException { + String path = pluginRef; + if (path.startsWith(getProtocol())) { + path = path.substring(getProtocol().length()); + } + + // resolve the file from the upload folder + Path uploadedFile = this.uploadFolder.resolve(path); + + if (Files.notExists(uploadedFile)) { + throw new PluginResolverNotFoundException(String.format("The provided file %s was not found as uploaded file", + uploadedFile.toString())); + } + return uploadedFile; + + } + + @Override + public String getProtocol() { + return "upload:"; + } + + +}