/******************************************************************************* * 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.jdt; import org.eclipse.che.api.builder.BuildStatus; import org.eclipse.che.api.builder.BuilderException; import org.eclipse.che.api.builder.dto.BuildOptions; import org.eclipse.che.api.builder.dto.BuildTaskDescriptor; import org.eclipse.che.api.core.rest.HttpJsonHelper; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.vfs.server.util.DeleteOnCloseFileInputStream; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.ZipUtils; import org.eclipse.che.commons.user.User; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.jdt.internal.core.JavaProject; import org.eclipse.che.jdt.internal.core.SearchableEnvironment; import org.eclipse.che.jdt.internal.core.SourceTypeElementInfo; import org.eclipse.che.vfs.impl.fs.LocalFSMountStrategy; import com.google.inject.name.Named; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.CodenvyCompilationUnitResolver; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.env.INameEnvironment; import org.eclipse.jdt.internal.compiler.env.ISourceType; import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.io.File; import java.io.FilterInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Rest service for WorkerNameEnvironment * The name environment provides a callback API that the compiler can use to look up types, compilation units, and packages in the * current environment * * @author Evgen Vidolob */ @javax.ws.rs.Path("java-name-environment/{ws-id}") public class RestNameEnvironment { /** Logger. */ private static final Logger LOG = LoggerFactory.getLogger(RestNameEnvironment.class); @Inject private LocalFSMountStrategy fsMountStrategy; @Inject private JavaProjectService javaProjectService; @Context private HttpServletRequest request; @Inject @Named("che.java.codeassistant.index.dir") private String temp; @PathParam("ws-id") @Inject private String wsId; @Inject @Named("api.endpoint") private String apiUrl; private static String getAuthenticationToken() { User user = EnvironmentContext.getCurrent().getUser(); if (user != null) { return user.getToken(); } return null; } @GET @Produces(MediaType.APPLICATION_JSON) @javax.ws.rs.Path("findTypeCompound") public String findTypeCompound(@QueryParam("compoundTypeName") String compoundTypeName, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); try { NameEnvironmentAnswer answer = environment.findType(getCharArrayFrom(compoundTypeName)); if (answer == null && compoundTypeName.contains("$")) { String innerName = compoundTypeName.substring(compoundTypeName.indexOf('$') + 1, compoundTypeName.length()); compoundTypeName = compoundTypeName.substring(0, compoundTypeName.indexOf('$')); answer = environment.findType(getCharArrayFrom(compoundTypeName)); if (!answer.isCompilationUnit()) return null; ICompilationUnit compilationUnit = answer.getCompilationUnit(); CompilationUnit result = getCompilationUnit(javaProject, environment, compilationUnit); AbstractTypeDeclaration o = (AbstractTypeDeclaration)result.types().get(0); ITypeBinding typeBinding = o.resolveBinding(); for (ITypeBinding binding : typeBinding.getDeclaredTypes()) { if (binding.getBinaryName().endsWith(innerName)) { typeBinding = binding; break; } } Map<TypeBinding, ?> bindings = (Map<TypeBinding, ?>)result.getProperty("compilerBindingsToASTBindings"); SourceTypeBinding binding = null; for (Map.Entry<TypeBinding, ?> entry : bindings.entrySet()) { if (entry.getValue().equals(typeBinding)) { binding = (SourceTypeBinding)entry.getKey(); break; } } return TypeBindingConvector.toJsonBinaryType(binding); } return processAnswer(answer, javaProject, environment); } catch (JavaModelException e) { if (LOG.isDebugEnabled()) { LOG.error("Can't parse class: ", e); } throw new WebApplicationException(); } } private JavaProject getJavaProject(String projectPath) { return javaProjectService.getOrCreateJavaProject(wsId, projectPath); } @GET @Produces(MediaType.APPLICATION_JSON) @javax.ws.rs.Path("findType") public String findType(@QueryParam("typename") String typeName, @QueryParam("packagename") String packageName, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); NameEnvironmentAnswer answer = environment.findType(typeName.toCharArray(), getCharArrayFrom(packageName)); try { return processAnswer(answer, javaProject, environment); } catch (JavaModelException e) { if (LOG.isDebugEnabled()) { LOG.error("Can't parse class: ", e); } throw new WebApplicationException(e); } } @GET @javax.ws.rs.Path("package") @Produces("text/plain") public String isPackage(@QueryParam("packagename") String packageName, @QueryParam("parent") String parentPackageName, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); return String.valueOf(environment.isPackage(getCharArrayFrom(parentPackageName), packageName.toCharArray())); } @GET @Path("findPackages") @Produces(MediaType.APPLICATION_JSON) public String findPackages(@QueryParam("packagename") String packageName, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); JsonSearchRequester requestor = new JsonSearchRequester(); environment.findPackages(packageName.toCharArray(), requestor); return requestor.toJsonString(); } @GET @javax.ws.rs.Path("findConstructor") @Produces(MediaType.APPLICATION_JSON) public String findConstructorDeclarations(@QueryParam("prefix") String prefix, @QueryParam("camelcase") boolean camelCaseMatch, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); JsonSearchRequester searchRequester = new JsonSearchRequester(); environment.findConstructorDeclarations(prefix.toCharArray(), camelCaseMatch, searchRequester, null); return searchRequester.toJsonString(); } @GET @javax.ws.rs.Path("findTypes") @Produces(MediaType.APPLICATION_JSON) public String findTypes(@QueryParam("qualifiedname") String qualifiedName, @QueryParam("findmembers") boolean findMembers, @QueryParam("camelcase") boolean camelCaseMatch, @QueryParam("searchfor") int searchFor, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); JsonSearchRequester searchRequester = new JsonSearchRequester(); environment.findTypes(qualifiedName.toCharArray(), findMembers, camelCaseMatch, searchFor, searchRequester); return searchRequester.toJsonString(); } @GET @javax.ws.rs.Path("findExactTypes") @Produces(MediaType.APPLICATION_JSON) public String findExactTypes(@QueryParam("missingsimplename") String missingSimpleName, @QueryParam("findmembers") boolean findMembers, @QueryParam("searchfor") int searchFor, @QueryParam("projectpath") String projectPath) { JavaProject javaProject = getJavaProject(projectPath); SearchableEnvironment environment = javaProject.getNameEnvironment(); JsonSearchRequester searchRequester = new JsonSearchRequester(); environment.findExactTypes(missingSimpleName.toCharArray(), findMembers, searchFor, searchRequester); return searchRequester.toJsonString(); } @GET @javax.ws.rs.Path("/update-dependencies-launch-task") @Produces(MediaType.APPLICATION_JSON) public BuildTaskDescriptor updateDependency(@QueryParam("projectpath") String projectPath, @QueryParam("force") boolean force, @Context UriInfo uriInfo) throws Exception { //project already has updated dependency's, so skip build if (javaProjectService.isProjectDependencyExist(wsId, projectPath) && !force) { BuildTaskDescriptor descriptor = DtoFactory.getInstance().createDto(BuildTaskDescriptor.class); descriptor.setStatus(BuildStatus.SUCCESSFUL); return descriptor; } File workspace = fsMountStrategy.getMountPath(wsId); File project = new File(workspace, projectPath); if (!project.exists()) { LOG.warn("Project doesn't exist in workspace: " + wsId + ", path: " + projectPath); throw new CodeAssistantException(500, "Project doesn't exist"); } String url = apiUrl + "/builder/" + wsId + "/dependencies"; return getDependencies(url, projectPath, "copy", null); } /** Get list of all package names in project */ @POST @javax.ws.rs.Path("/update-dependencies-wait-build-end") @Produces(MediaType.APPLICATION_JSON) public void waitUpdateDependencyBuildEnd(@QueryParam("projectpath") String projectPath, BuildTaskDescriptor descriptor, @Context UriInfo uriInfo) throws Exception { // call to wait-for-build-finish method try { BuildTaskDescriptor finishedBuildStatus = waitTaskFinish(descriptor); if (finishedBuildStatus.getStatus() == BuildStatus.FAILED) { buildFailed(finishedBuildStatus); } javaProjectService.removeProject(wsId, projectPath); File projectDepDir = new File(temp, wsId + projectPath); projectDepDir.mkdirs(); Link downloadLink = findLink("download result", finishedBuildStatus.getLinks()); if (downloadLink != null) { File zip = doDownload(downloadLink.getHref(), projectPath, "dependencies.zip"); ZipUtils.unzip(new DeleteOnCloseFileInputStream(zip), projectDepDir); } BuildOptions buildOptions = DtoFactory.getInstance().createDto(BuildOptions.class); buildOptions.getOptions().put("-Dclassifier", "sources"); String url = apiUrl + "/builder/" + wsId + "/dependencies"; BuildTaskDescriptor dependencies = getDependencies(url, projectPath, "copy", buildOptions); BuildTaskDescriptor buildTaskDescriptor = waitTaskFinish(dependencies); if (finishedBuildStatus.getStatus() == BuildStatus.FAILED) { buildFailed(finishedBuildStatus); } File projectSourcesJars = new File(projectDepDir, "sources"); projectSourcesJars.mkdirs(); downloadLink = findLink("download result", buildTaskDescriptor.getLinks()); if (downloadLink != null) { File zip = doDownload(downloadLink.getHref(), projectPath, "sources.zip"); ZipUtils.unzip(new DeleteOnCloseFileInputStream(zip), projectSourcesJars); } //create JavaProject adn put it into cache javaProjectService.getOrCreateJavaProject(wsId, projectPath); } catch (Throwable debug) { LOG.error("RestNameEnvironment", debug); throw new WebApplicationException(debug); } } private File doDownload(String downloadURL, String projectPath, String zipName) throws IOException { HttpURLConnection http = null; HttpStream stream = null; try { URI uri = UriBuilder.fromUri(downloadURL).queryParam("token", getAuthenticationToken()).build(); http = (HttpURLConnection)uri.toURL().openConnection(); http.setRequestMethod("GET"); int responseCode = http.getResponseCode(); if (responseCode != 200) { throw new IOException("Your project referenced a zipped dependency that cannot be downloaded."); } // Connection closed automatically when input stream closed. // If IOException or BuilderException occurs then connection closed immediately. stream = new HttpStream(http); java.nio.file.Path path = Paths.get(temp, wsId + projectPath + "/" + zipName); Files.copy(stream, path); return path.toFile(); } catch (MalformedURLException e) { throw e; } catch (IOException ioe) { if (http != null) { http.disconnect(); } throw ioe; } finally { if (stream != null) { stream.close(); } } } private void buildFailed(@Nullable BuildTaskDescriptor buildStatus) throws BuilderException { if (buildStatus != null) { Link logLink = findLink("view build log", buildStatus.getLinks()); LOG.error("Build failed see more detail here: " + logLink.getHref()); throw new BuilderException( "Build failed see more detail here: <a href=\"" + logLink.getHref() + "\" target=\"_blank\">" + logLink.getHref() + "</a>." ); } throw new BuilderException("Build failed"); } @Nullable private Link findLink(@Nonnull String rel, List<Link> links) { for (Link link : links) { if (link.getRel().equals(rel)) { return link; } } return null; } @Nonnull private BuildTaskDescriptor waitTaskFinish(@Nonnull BuildTaskDescriptor buildDescription) throws Exception { BuildTaskDescriptor request = buildDescription; final int sleepTime = 500; Link statusLink = findLink("get status", buildDescription.getLinks()); if (statusLink != null) { while (request.getStatus() == BuildStatus.IN_PROGRESS || request.getStatus() == BuildStatus.IN_QUEUE) { try { Thread.sleep(sleepTime); } catch (InterruptedException ignored) { } request = HttpJsonHelper.request(BuildTaskDescriptor.class, statusLink); } } return request; } @Nonnull private BuildTaskDescriptor getDependencies(@Nonnull String url, @Nonnull String projectName, @Nonnull String analyzeType, @Nullable BuildOptions options) throws Exception { Pair<String, String> projectParam = Pair.of("project", projectName); Pair<String, String> typeParam = Pair.of("type", analyzeType); return HttpJsonHelper.request(BuildTaskDescriptor.class, url, "POST", options, projectParam, typeParam); } private String processAnswer(NameEnvironmentAnswer answer, IJavaProject project, INameEnvironment environment) throws JavaModelException { if (answer == null) return null; if (answer.isBinaryType()) { IBinaryType binaryType = answer.getBinaryType(); return BinaryTypeConvector.toJsonBinaryType(binaryType); } else if (answer.isCompilationUnit()) { ICompilationUnit compilationUnit = answer.getCompilationUnit(); return getSourceTypeInfo(project, environment, compilationUnit); } else if (answer.isSourceType()) { ISourceType[] sourceTypes = answer.getSourceTypes(); if (sourceTypes.length == 1) { ISourceType sourceType = sourceTypes[0]; SourceTypeElementInfo elementInfo = (SourceTypeElementInfo)sourceType; IType handle = elementInfo.getHandle(); org.eclipse.jdt.core.ICompilationUnit unit = handle.getCompilationUnit(); return getSourceTypeInfo(project, environment, (ICompilationUnit)unit); } } return null; } private String getSourceTypeInfo(IJavaProject project, INameEnvironment environment, ICompilationUnit compilationUnit) throws JavaModelException { CompilationUnit result = getCompilationUnit(project, environment, compilationUnit); BindingASTVisitor visitor = new BindingASTVisitor(); result.accept(visitor); Map<TypeBinding, ?> bindings = (Map<TypeBinding, ?>)result.getProperty("compilerBindingsToASTBindings"); SourceTypeBinding binding = null; for (Map.Entry<TypeBinding, ?> entry : bindings.entrySet()) { if (entry.getValue().equals(visitor.typeBinding)) { binding = (SourceTypeBinding)entry.getKey(); break; } } if (binding == null) return null; return TypeBindingConvector.toJsonBinaryType(binding); } private CompilationUnit getCompilationUnit(IJavaProject project, INameEnvironment environment, ICompilationUnit compilationUnit) throws JavaModelException { int flags = 0; flags |= org.eclipse.jdt.core.ICompilationUnit.ENABLE_STATEMENTS_RECOVERY; flags |= org.eclipse.jdt.core.ICompilationUnit.IGNORE_METHOD_BODIES; flags |= org.eclipse.jdt.core.ICompilationUnit.ENABLE_BINDINGS_RECOVERY; HashMap<String, String> opts = new HashMap<>(javaProjectService.getOptions()); CompilationUnitDeclaration compilationUnitDeclaration = CodenvyCompilationUnitResolver.resolve(compilationUnit, project, environment, opts, flags, null); return CodenvyCompilationUnitResolver.convert( compilationUnitDeclaration, compilationUnit.getContents(), flags, opts); } private char[][] getCharArrayFrom(String list) { if(list.isEmpty()){ return null; } String[] strings = list.split(","); char[][] arr = new char[strings.length][]; for (int i = 0; i < strings.length; i++) { String s = strings[i]; arr[i] = s.toCharArray(); } return arr; } /** Stream that automatically close HTTP connection when all data ends. */ private static class HttpStream extends FilterInputStream { private final HttpURLConnection http; private boolean closed; private HttpStream(HttpURLConnection http) throws IOException { super(http.getInputStream()); this.http = http; } @Override public int read() throws IOException { int r = super.read(); if (r == -1) { close(); } return r; } @Override public int read(byte[] b) throws IOException { int r = super.read(b); if (r == -1) { close(); } return r; } @Override public int read(byte[] b, int off, int len) throws IOException { int r = super.read(b, off, len); if (r == -1) { close(); } return r; } @Override public void close() throws IOException { if (closed) { return; } try { super.close(); } finally { http.disconnect(); closed = true; } } } }