diff --git a/.gitignore b/.gitignore index 6efbd2a157..7ceedda9d5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ **/*.iml .gradle/ **/build/ + +# Whitelist iml files needed for tests +!**/testData/**/*.iml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 034d576512..aa591fbb2d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -27,6 +27,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 38523fdee3..1612def63e 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,3 @@ If you wish to build this plugin from source, please see the **None yet** - diff --git a/build.gradle b/build.gradle index 0e2327f7b3..5284047893 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,7 @@ subprojects { apply plugin: 'org.jetbrains.intellij' intellij { + type = ideaEdition version = ideaVersion updateSinceUntilBuild = false downloadSources = true diff --git a/google-cloud-tools-plugin/build.gradle b/google-cloud-tools-plugin/build.gradle index 3d51ab8725..720843ef54 100644 --- a/google-cloud-tools-plugin/build.gradle +++ b/google-cloud-tools-plugin/build.gradle @@ -18,7 +18,7 @@ sourceSets.main.java.srcDirs = ['src'] sourceSets.main.resources.srcDirs = ['resources'] sourceSets.test.java.srcDirs = ['testSrc'] -sourceSets.test.resources.srcDirs = ['testResources'] +sourceSets.test.resources.srcDirs = ['testResources', 'testData'] intellij { pluginName = 'google-cloud-tools' @@ -31,12 +31,25 @@ intellij { project.afterEvaluate { prepareSandbox.dependsOn ':google-account-plugin:prepareSandbox' + + //todo: currently we need this to include 'ultimate' module to the plugin zip; it would be better to support such inclusion in 'intellij' plugin + def task = project.tasks.findByName("prepareSandbox") + task.dependsOn project('ultimate').jar + task.doLast { + copy { + from files(project('ultimate').jar) + into "$task.destinationDir/lib" + } + } } dependencies { compile(project(':google-account-plugin')) compile(project(':common-lib')) + compile project('jps-plugin') + compile project('runtime') + compile 'com.google.apis:google-api-services-clouddebugger:v2-rev7-1.21.0' compile 'com.google.apis:google-api-services-cloudresourcemanager:v1beta1-rev12-1.21.0' compile ('com.google.cloud.tools:appengine-plugins-core:0.1.0') { @@ -48,9 +61,19 @@ dependencies { testRuntime files('../google-account-plugin/lib/google-gct-login-context-ij-pg.jar') } +task testJar(type: Jar, dependsOn: testClasses) { + baseName = "${project.archivesBaseName}-tests" + from sourceSets.test.output +} + configurations { // this is already obtained from google-api-java-client-min-repackaged compile.exclude group: 'com.google.api-client', module: 'google-api-client' + tests +} + +artifacts { + tests testJar } test { diff --git a/google-cloud-tools-plugin/jps-plugin/build.gradle b/google-cloud-tools-plugin/jps-plugin/build.gradle new file mode 100644 index 0000000000..515a91289b --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/build.gradle @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +sourceSets.main.java.srcDirs = ['src'] +sourceSets.main.resources.srcDirs = ['resources'] +sourceSets.test.java.srcDirs = ['testSrc'] +sourceSets.test.resources.srcDirs = ['testData'] + +jar.archiveName = "google-app-engine-jps-plugin.jar" + +// Overriding paths in root build file since this module is nested one level deeper +pmd { + ruleSetFiles = files('../../custom-pmd-ruleset.xml'); +} +findbugs { + excludeFilter = file('../../findbugs-excludefilter.xml') +} + +dependencies { + compile project(':google-cloud-tools-plugin:runtime') + testCompile group: 'com.jetbrains.intellij.idea', name:'jps-build-test', version: ideaVersion +} \ No newline at end of file diff --git a/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService new file mode 100644 index 0000000000..f734e6bb0c --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService @@ -0,0 +1,17 @@ +# +# Copyright 2000-2016 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.jetbrains.jps.appengine.model.impl.JpsAppEngineExtensionServiceImpl \ No newline at end of file diff --git a/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.incremental.BuilderService b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.incremental.BuilderService new file mode 100644 index 0000000000..3189a1f578 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.incremental.BuilderService @@ -0,0 +1,17 @@ +# +# Copyright 2000-2016 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.jetbrains.jps.appengine.builder.AppEngineEnhancerBuilderService \ No newline at end of file diff --git a/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension new file mode 100644 index 0000000000..feecd3ac91 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/resources/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension @@ -0,0 +1 @@ +org.jetbrains.jps.appengine.model.impl.JpsAppEngineModelSerializerExtension \ No newline at end of file diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilder.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilder.java new file mode 100644 index 0000000000..85ef74fd73 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilder.java @@ -0,0 +1,210 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.builder; + +import com.intellij.appengine.rt.EnhancerRunner; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.execution.ParametersListUtil; + +import gnu.trove.THashSet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.ModuleChunk; +import org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.appengine.model.PersistenceApi; +import org.jetbrains.jps.builders.DirtyFilesHolder; +import org.jetbrains.jps.builders.FileProcessor; +import org.jetbrains.jps.builders.java.JavaBuilderUtil; +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; +import org.jetbrains.jps.builders.logging.ProjectBuilderLogger; +import org.jetbrains.jps.incremental.BuilderCategory; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ExternalProcessUtil; +import org.jetbrains.jps.incremental.ModuleBuildTarget; +import org.jetbrains.jps.incremental.ModuleLevelBuilder; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.model.JpsDummyElement; +import org.jetbrains.jps.model.java.JpsJavaExtensionService; +import org.jetbrains.jps.model.java.JpsJavaSdkType; +import org.jetbrains.jps.model.library.sdk.JpsSdk; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.util.JpsPathUtil; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author nik + */ +public class AppEngineEnhancerBuilder extends ModuleLevelBuilder { + + public static final String NAME = "Google AppEngine Enhancer"; + + public AppEngineEnhancerBuilder() { + super(BuilderCategory.CLASS_POST_PROCESSOR); + } + + @Override + public ExitCode build(final CompileContext context, + ModuleChunk chunk, + DirtyFilesHolder dirtyFilesHolder, + OutputConsumer outputConsumer) + throws ProjectBuildException, IOException { + + boolean doneSomething = false; + for (final JpsModule module : chunk.getModules()) { + JpsAppEngineModuleExtension extension = JpsAppEngineExtensionService.getInstance() + .getExtension(module); + if (extension != null && extension.isRunEnhancerOnMake()) { + doneSomething |= processModule(context, dirtyFilesHolder, extension); + } + } + + return doneSomething ? ExitCode.OK : ExitCode.NOTHING_DONE; + } + + @Override + public List getCompilableFileExtensions() { + return Collections.emptyList(); + } + + private static boolean processModule(final CompileContext context, + DirtyFilesHolder dirtyFilesHolder, + JpsAppEngineModuleExtension extension) throws IOException, ProjectBuildException { + final Set roots = new THashSet(FileUtil.FILE_HASHING_STRATEGY); + for (String path : extension.getFilesToEnhance()) { + roots.add(new File(FileUtil.toSystemDependentName(path))); + } + + final List pathsToProcess = new ArrayList(); + dirtyFilesHolder + .processDirtyFiles(new FileProcessor() { + @Override + public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor root) + throws IOException { + if (JpsPathUtil.isUnder(roots, file)) { + Collection outputs = context.getProjectDescriptor().dataManager + .getSourceToOutputMap(target).getOutputs(file.getAbsolutePath()); + if (outputs != null) { + pathsToProcess.addAll(outputs); + } + } + return true; + } + }); + if (pathsToProcess.isEmpty()) { + return false; + } + + JpsModule module = extension.getModule(); + context.processMessage( + new ProgressMessage("Enhancing classes in module '" + module.getName() + "'...")); + + List classpath = new ArrayList(); + classpath.add(extension.getToolsApiJarPath()); + classpath.add(PathManager.getJarPathForClass(EnhancerRunner.class)); + boolean removeOrmJars = Boolean + .parseBoolean(System.getProperty("jps.appengine.enhancer.remove.orm.jars", "true")); + for (File file : JpsJavaExtensionService.dependencies(module).recursively().compileOnly() + .productionOnly().classes().getRoots()) { + if (removeOrmJars && FileUtil.isAncestor(new File(extension.getOrmLibPath()), file, true)) { + continue; + } + classpath.add(file.getAbsolutePath()); + } + + List programParams = new ArrayList(); + final File argsFile = FileUtil.createTempFile("appEngineEnhanceFiles", ".txt"); + PrintWriter writer = new PrintWriter(argsFile, StandardCharsets.UTF_8.name()); + try { + for (String path : pathsToProcess) { + writer.println(FileUtil.toSystemDependentName(path)); + } + } finally { + writer.close(); + } + + programParams.add(argsFile.getAbsolutePath()); + programParams.add("com.google.appengine.tools.enhancer.Enhance"); + programParams.add("-api"); + PersistenceApi api = extension.getPersistenceApi(); + programParams.add(api.getEnhancerApiName()); + if (api.getEnhancerVersion() == 2) { + programParams.add("-enhancerVersion"); + programParams.add("v2"); + } + programParams.add("-v"); + + List vmParams = Collections.singletonList("-Xmx256m"); + + JpsSdk sdk = JavaBuilderUtil.ensureModuleHasJdk(module, context, NAME); + List commandLine = ExternalProcessUtil + .buildJavaCommandLine(JpsJavaSdkType.getJavaExecutable(sdk), EnhancerRunner.class.getName(), + Collections.emptyList(), classpath, vmParams, programParams); + + Process process = new ProcessBuilder(commandLine).start(); + ExternalEnhancerProcessHandler handler = new ExternalEnhancerProcessHandler(process, + commandLine, context); + handler.startNotify(); + handler.waitFor(); + ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger(); + if (logger.isEnabled()) { + logger.logCompiledPaths(pathsToProcess, NAME, "Enhancing classes:"); + } + return true; + } + + + @NotNull + @Override + public String getPresentableName() { + return NAME; + } + + private static class ExternalEnhancerProcessHandler extends EnhancerProcessHandlerBase { + + private final CompileContext myContext; + + public ExternalEnhancerProcessHandler(Process process, List commandLine, + CompileContext context) { + super(process, ParametersListUtil.join(commandLine), null); + myContext = context; + } + + @Override + protected void reportInfo(String message) { + myContext.processMessage(new CompilerMessage(NAME, BuildMessage.Kind.INFO, message)); + } + + @Override + protected void reportError(String message) { + myContext.processMessage(new CompilerMessage(NAME, BuildMessage.Kind.ERROR, message)); + } + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilderService.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilderService.java new file mode 100644 index 0000000000..95b9fd463e --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/AppEngineEnhancerBuilderService.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.builder; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.incremental.BuilderService; +import org.jetbrains.jps.incremental.ModuleLevelBuilder; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineEnhancerBuilderService extends BuilderService { + @NotNull + @Override + public List createModuleLevelBuilders() { + return Collections.singletonList(new AppEngineEnhancerBuilder()); + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/EnhancerProcessHandlerBase.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/EnhancerProcessHandlerBase.java new file mode 100644 index 0000000000..541be2d77a --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/builder/EnhancerProcessHandlerBase.java @@ -0,0 +1,138 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.builder; + +import com.intellij.execution.process.BaseOSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.containers.FactoryMap; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * @author nik + */ +public abstract class EnhancerProcessHandlerBase extends BaseOSProcessHandler { + + private static final Logger LOG = Logger + .getInstance("#com.intellij.appengine.enhancement.EnhancerProcessHandler"); + private FactoryMap myParsers + = new FactoryMap() { + @Override + protected EnhancerOutputParser create(Key key) { + return new EnhancerOutputParser(ProcessOutputTypes.STDERR.equals(key)); + } + }; + + public EnhancerProcessHandlerBase(Process process, @NotNull String commandLine, Charset charset) { + super(process, commandLine, charset); + addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(ProcessEvent event) { + final int exitCode = event.getExitCode(); + if (exitCode != 0) { + reportError("Enhancement process terminated with exit code " + exitCode); + } + } + }); + } + + @Override + public void notifyTextAvailable(String text, Key outputType) { + super.notifyTextAvailable(text, outputType); + myParsers.get(outputType).appendText(text); + } + + protected abstract void reportInfo(String message); + + protected abstract void reportError(String message); + + private class EnhancerOutputParser { + + @NonNls + private static final String PLEASE_SEE_THE_LOGS_PREFIX = "Please see the logs ["; + private StringBuilder myBuffer = new StringBuilder(); + private final boolean myErrorStream; + + public EnhancerOutputParser(boolean errorStream) { + myErrorStream = errorStream; + } + + + public void appendText(String text) { + myBuffer.append(text); + int start = 0; + while (true) { + int lineEnd1 = myBuffer.indexOf("\n", start); + int lineEnd2 = myBuffer.indexOf("\r", start); + if (lineEnd1 == -1 && lineEnd2 == -1) { + break; + } + + int lineEnd = + lineEnd1 == -1 ? lineEnd2 : lineEnd2 == -1 ? lineEnd1 : Math.min(lineEnd1, lineEnd2); + parseLine(myBuffer.substring(start, lineEnd).trim()); + start = lineEnd + 1; + } + + myBuffer.delete(0, start); + } + + private void parseLine(String line) { + LOG.debug(myErrorStream ? "[err] " + line : line); + if (myErrorStream) { + reportError(line); + return; + } + + if (line.startsWith("Encountered a problem: ")) { + reportError(line); + } else if (line.startsWith(PLEASE_SEE_THE_LOGS_PREFIX)) { + if (!showLogFileContent(line)) { + reportError(line); + } + } else if (line.startsWith("DataNucleus Enhancer completed")) { + reportInfo(line); + } + } + + private boolean showLogFileContent(String line) { + final int i = line.lastIndexOf(']'); + if (i != -1) { + File logFile = new File(line.substring(PLEASE_SEE_THE_LOGS_PREFIX.length(), i)); + if (logFile.exists()) { + try { + reportError(FileUtil.loadFile(logFile)); + return true; + } catch (IOException ignored) { + // ignored + } + } + } + return false; + } + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineExtensionService.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineExtensionService.java new file mode 100644 index 0000000000..7f4cdbc30e --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineExtensionService.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.service.JpsServiceManager; + +/** + * @author nik + */ +public abstract class JpsAppEngineExtensionService { + public static JpsAppEngineExtensionService getInstance() { + return JpsServiceManager.getInstance().getService(JpsAppEngineExtensionService.class); + } + + @Nullable + public abstract JpsAppEngineModuleExtension getExtension(@NotNull JpsModule module); +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineModuleExtension.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineModuleExtension.java new file mode 100644 index 0000000000..ba3abd9151 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/JpsAppEngineModuleExtension.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model; + +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.module.JpsModule; + +import java.util.List; + +/** + * @author nik + */ +public interface JpsAppEngineModuleExtension extends JpsElement { + JpsModule getModule(); + + String getOrmLibPath(); + + String getSdkHomePath(); + + boolean isRunEnhancerOnMake(); + + List getFilesToEnhance(); + + PersistenceApi getPersistenceApi(); + + String getToolsApiJarPath(); +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/PersistenceApi.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/PersistenceApi.java new file mode 100644 index 0000000000..ff6efc031c --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/PersistenceApi.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model; + +/** +* @author nik +*/ +public enum PersistenceApi { + JDO("JDO 2", "JDO", 1), JDO3("JDO 3", "JDO", 2), JPA("JPA 1", "JPA", 1), JPA2("JPA 2", "JPA", 2); + private final String myDisplayName; + private final String myEnhancerApiName; + private final int myEnhancerVersion; + + PersistenceApi(String displayName, String enhancerApiName, int enhancerVersion) { + myDisplayName = displayName; + myEnhancerApiName = enhancerApiName; + myEnhancerVersion = enhancerVersion; + } + + public String getDisplayName() { + return myDisplayName; + } + + public String getEnhancerApiName() { + return myEnhancerApiName; + } + + public int getEnhancerVersion() { + return myEnhancerVersion; + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/AppEngineModuleExtensionProperties.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/AppEngineModuleExtensionProperties.java new file mode 100644 index 0000000000..71bafb5ec9 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/AppEngineModuleExtensionProperties.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model.impl; + +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.jps.appengine.model.PersistenceApi; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineModuleExtensionProperties { + @Tag("sdk-home-path") + public String mySdkHomePath = ""; + + @Tag("run-enhancer-on-make") + public boolean myRunEnhancerOnMake = false; + + @Tag("files-to-enhance") + @AbstractCollection(surroundWithTag = false, elementTag = "file", elementValueAttribute = "path") + public List myFilesToEnhance = new ArrayList(); + + @Tag("persistence-api") + public PersistenceApi myPersistenceApi = PersistenceApi.JDO; +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineExtensionServiceImpl.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineExtensionServiceImpl.java new file mode 100644 index 0000000000..3ba8409537 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineExtensionServiceImpl.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.model.module.JpsModule; + +/** + * @author nik + */ +public class JpsAppEngineExtensionServiceImpl extends JpsAppEngineExtensionService { + @Nullable + @Override + public JpsAppEngineModuleExtension getExtension(@NotNull JpsModule module) { + return module.getContainer().getChild(JpsAppEngineModuleExtensionImpl.ROLE); + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModelSerializerExtension.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModelSerializerExtension.java new file mode 100644 index 0000000000..bc24ee5963 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModelSerializerExtension.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model.impl; + +import com.intellij.util.xmlb.XmlSerializer; + +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; +import org.jetbrains.jps.model.serialization.facet.JpsFacetConfigurationSerializer; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class JpsAppEngineModelSerializerExtension extends JpsModelSerializerExtension { + + @NotNull + @Override + public List> getFacetConfigurationSerializers() { + return Collections.singletonList(new JpsAppEngineModuleExtensionSerializer()); + } + + private static class JpsAppEngineModuleExtensionSerializer extends + JpsFacetConfigurationSerializer { + + public JpsAppEngineModuleExtensionSerializer() { + super(JpsAppEngineModuleExtensionImpl.ROLE, "google-app-engine", "Google App Engine"); + } + + @Override + protected JpsAppEngineModuleExtension loadExtension(@NotNull Element facetConfigurationElement, + String name, + JpsElement parent, + JpsModule module) { + AppEngineModuleExtensionProperties properties = XmlSerializer + .deserialize(facetConfigurationElement, AppEngineModuleExtensionProperties.class); + return new JpsAppEngineModuleExtensionImpl( + properties != null ? properties : new AppEngineModuleExtensionProperties()); + } + + @Override + protected void saveExtension(JpsAppEngineModuleExtension extension, + Element facetConfigurationTag, JpsModule module) { + XmlSerializer.serializeInto(((JpsAppEngineModuleExtensionImpl) extension).getProperties(), + facetConfigurationTag); + } + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModuleExtensionImpl.java b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModuleExtensionImpl.java new file mode 100644 index 0000000000..6a4d63edbf --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/src/org/jetbrains/jps/appengine/model/impl/JpsAppEngineModuleExtensionImpl.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jps.appengine.model.impl; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.xmlb.XmlSerializerUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.appengine.model.PersistenceApi; +import org.jetbrains.jps.incremental.artifacts.impl.JpsArtifactPathUtil; +import org.jetbrains.jps.model.JpsElementChildRole; +import org.jetbrains.jps.model.ex.JpsElementBase; +import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; +import org.jetbrains.jps.model.module.JpsModule; + +import java.util.List; + +/** + * @author nik + */ +public class JpsAppEngineModuleExtensionImpl extends + JpsElementBase implements + JpsAppEngineModuleExtension { + + public static final JpsElementChildRole ROLE + = JpsElementChildRoleBase.create("AppEngine"); + public static final String LIB_APPENGINE_TOOLS_API_JAR = "/lib/appengine-tools-api.jar"; + private AppEngineModuleExtensionProperties myProperties; + + public JpsAppEngineModuleExtensionImpl(AppEngineModuleExtensionProperties properties) { + myProperties = properties; + } + + private JpsAppEngineModuleExtensionImpl(JpsAppEngineModuleExtensionImpl original) { + myProperties = XmlSerializerUtil.createCopy(original.myProperties); + } + + public AppEngineModuleExtensionProperties getProperties() { + return myProperties; + } + + @Override + public JpsModule getModule() { + return (JpsModule) getParent(); + } + + @NotNull + @Override + public JpsAppEngineModuleExtensionImpl createCopy() { + return new JpsAppEngineModuleExtensionImpl(this); + } + + @Override + public void applyChanges(@NotNull JpsAppEngineModuleExtensionImpl modified) { + XmlSerializerUtil.copyBean(modified.myProperties, myProperties); + } + + @Override + public String getToolsApiJarPath() { + return FileUtil.toSystemDependentName( + JpsArtifactPathUtil.appendToPath(getSdkHomePath(), LIB_APPENGINE_TOOLS_API_JAR)); + } + + @Override + public String getOrmLibPath() { + return FileUtil + .toSystemDependentName(JpsArtifactPathUtil.appendToPath(getSdkHomePath(), "/lib/user/orm")); + } + + @Override + public String getSdkHomePath() { + return myProperties.mySdkHomePath; + } + + @Override + public boolean isRunEnhancerOnMake() { + return myProperties.myRunEnhancerOnMake; + } + + @Override + public List getFilesToEnhance() { + return myProperties.myFilesToEnhance; + } + + @Override + public PersistenceApi getPersistenceApi() { + return myProperties.myPersistenceApi; + } + + +} diff --git a/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.iml b/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.iml new file mode 100644 index 0000000000..ec4c14c3f6 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.iml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + JPA2 + true + $USER_HOME$/applications/appengine-java-sdk-1.7.3 + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.ipr b/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.ipr new file mode 100644 index 0000000000..b0ccbee1e7 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/testData/serialization/appEngine/appEngine.ipr @@ -0,0 +1,82 @@ + + + + + + + + $PROJECT_DIR$/out/artifacts/appEngineEnhancerV2_Web_exploded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/jps-plugin/testSrc/com/google/appengine/tools/enhancer/Enhance.java b/google-cloud-tools-plugin/jps-plugin/testSrc/com/google/appengine/tools/enhancer/Enhance.java new file mode 100644 index 0000000000..58bd92f151 --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/testSrc/com/google/appengine/tools/enhancer/Enhance.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.appengine.tools.enhancer; + +/** + * @author nik + */ +public class Enhance { + public static void main(String[] args) { + System.out.println("Enhancing finished"); + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/AppEngineEnhancerBuilderTest.java b/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/AppEngineEnhancerBuilderTest.java new file mode 100644 index 0000000000..3da074751c --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/AppEngineEnhancerBuilderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.appengine; + +import com.google.appengine.tools.enhancer.Enhance; +import com.intellij.openapi.application.PathManager; +import com.intellij.util.PathUtil; +import org.jetbrains.jps.appengine.builder.AppEngineEnhancerBuilder; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.appengine.model.impl.AppEngineModuleExtensionProperties; +import org.jetbrains.jps.appengine.model.impl.JpsAppEngineModuleExtensionImpl; +import org.jetbrains.jps.builders.JpsBuildTestCase; +import org.jetbrains.jps.model.java.JpsJavaLibraryType; +import org.jetbrains.jps.model.library.JpsLibrary; +import org.jetbrains.jps.model.library.JpsOrderRootType; +import org.jetbrains.jps.model.module.JpsModule; + +import java.io.File; + +/** + * @author nik + */ +public class AppEngineEnhancerBuilderTest extends JpsBuildTestCase { + public void testChangeFile() { + String file = createFile("src/A.java", "class A{}"); + addAppEngineModule("a", true, PathUtil.getParentPath(file)); + makeAll(); + assertEnhanced("out/production/a/A.class"); + + makeAll(); + assertEnhanced(); + + change(file); + makeAll(); + assertEnhanced("out/production/a/A.class"); + } + + public void testDoNotRunEnhancerIfDisabled() { + String file = createFile("src/A.java", "class A{}"); + addAppEngineModule("a", false, PathUtil.getParentPath(file)); + makeAll(); + assertEnhanced(); + } + + private void assertEnhanced(final String... paths) { + assertCompiled(AppEngineEnhancerBuilder.NAME, paths); + } + + private void addAppEngineModule(final String moduleName, final boolean runEnhancerOnMake, String srcRoot) { + JpsModule module = addModule(moduleName, srcRoot); + AppEngineModuleExtensionProperties properties = new AppEngineModuleExtensionProperties(); + properties.myRunEnhancerOnMake = runEnhancerOnMake; + properties.myFilesToEnhance.add(srcRoot); + JpsAppEngineModuleExtension extension = new JpsAppEngineModuleExtensionImpl(properties); + module.getContainer().setChild(JpsAppEngineModuleExtensionImpl.ROLE, extension); + addEnhancerLibrary(module); + } + + private static void addEnhancerLibrary(JpsModule module) { + String path = PathManager.getJarPathForClass(Enhance.class); + JpsLibrary library = module.addModuleLibrary("appengine-enhancer", JpsJavaLibraryType.INSTANCE); + library.addRoot(new File(path), JpsOrderRootType.COMPILED); + module.getDependenciesList().addLibraryDependency(library); + } +} diff --git a/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/JpsAppEngineSerializationTest.java b/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/JpsAppEngineSerializationTest.java new file mode 100644 index 0000000000..375a927ebb --- /dev/null +++ b/google-cloud-tools-plugin/jps-plugin/testSrc/org/jetbrains/jps/appengine/JpsAppEngineSerializationTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.appengine; + +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.jps.appengine.model.JpsAppEngineExtensionService; +import org.jetbrains.jps.appengine.model.JpsAppEngineModuleExtension; +import org.jetbrains.jps.appengine.model.PersistenceApi; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.model.serialization.JpsSerializationTestCase; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; + +/** + * @author nik + */ +public class JpsAppEngineSerializationTest extends JpsSerializationTestCase { + public static final String PROJECT_PATH = "serialization/appEngine"; + + public void testLoad() { + loadProject(PROJECT_PATH + "/appEngine.ipr"); + JpsModule module = assertOneElement(myProject.getModules()); + assertEquals("appEngine", module.getName()); + JpsAppEngineModuleExtension extension = JpsAppEngineExtensionService.getInstance().getExtension(module); + assertNotNull(extension); + assertEquals(PersistenceApi.JPA2, extension.getPersistenceApi()); + assertEquals(FileUtil.toSystemIndependentName(getTestDataFileAbsolutePath(PROJECT_PATH) + "/src"), assertOneElement(extension.getFilesToEnhance())); + assertTrue(extension.isRunEnhancerOnMake()); + } + + @Override + protected String getTestDataFileAbsolutePath(String relativePath) { + return new File(getTestDataPath(), relativePath).getAbsolutePath(); + } + + public static File getTestDataPath() { + try { + URL resource = JpsAppEngineSerializationTest.class.getResource("/serialization"); + File testDataRoot = Paths.get(resource.toURI()).toFile().getParentFile(); + return testDataRoot; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/google-cloud-tools-plugin/resources/META-INF/google-app-engine-maven-support.xml b/google-cloud-tools-plugin/resources/META-INF/google-app-engine-maven-support.xml new file mode 100644 index 0000000000..ee5bb11ea5 --- /dev/null +++ b/google-cloud-tools-plugin/resources/META-INF/google-app-engine-maven-support.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/google-cloud-tools-plugin/resources/META-INF/plugin.xml b/google-cloud-tools-plugin/resources/META-INF/plugin.xml index 3db2e382e9..d8094d0013 100644 --- a/google-cloud-tools-plugin/resources/META-INF/plugin.xml +++ b/google-cloud-tools-plugin/resources/META-INF/plugin.xml @@ -26,6 +26,10 @@ Code inspections for App Engine Java code. + com.intellij.javaee + com.intellij.gwt + org.jetbrains.idea.maven + com.intellij.modules.platform com.intellij.modules.lang com.intellij.modules.vcs @@ -45,6 +49,10 @@ Code inspections for App Engine Java code. + + + + @@ -59,11 +67,30 @@ Code inspections for App Engine Java code. serviceImplementation="com.google.cloud.tools.intellij.IdeaCloudToolsPluginInfoService"/> + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/resources/data/methodsBlacklist.txt b/google-cloud-tools-plugin/resources/data/methodsBlacklist.txt new file mode 100644 index 0000000000..1462c5ba4c --- /dev/null +++ b/google-cloud-tools-plugin/resources/data/methodsBlacklist.txt @@ -0,0 +1,3 @@ +java.lang.System:exit,gc,runFinalization,runFinalizersOnExit,load,loadLibrary,setSecurityManager,inheritedChannel,console +java.lang.Thread:new +java.lang.ThreadGroup:new diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.ft b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.ft new file mode 100644 index 0000000000..dd9cb0ebc5 --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.ft @@ -0,0 +1,4 @@ + + + + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.html b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.html new file mode 100644 index 0000000000..8ebc0c05a4 --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineApplication.xml.html @@ -0,0 +1,13 @@ + + + + + + +
+ + This is a built-in template used to create appengine-application.xml descriptor for Google App Engine application + +
+ + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.ft b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.ft new file mode 100644 index 0000000000..e42487fda4 --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.ft @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.html b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.html new file mode 100644 index 0000000000..693d6e48c0 --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJdoConfig.xml.html @@ -0,0 +1,13 @@ + + + + + + +
+ + This is a built-in template used to create jdoconfig.xml descriptor for Google App Engine application + +
+ + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.ft b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.ft new file mode 100644 index 0000000000..402a83b9cc --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.ft @@ -0,0 +1,16 @@ + + + + + org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider + + + + + + + + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.html b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.html new file mode 100644 index 0000000000..bfb7fee46e --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineJpaConfig.xml.html @@ -0,0 +1,13 @@ + + + + + + +
+ + This is a built-in template used to create persistence.xml descriptor for Google App Engine application + +
+ + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.ft b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.ft new file mode 100644 index 0000000000..a013642c74 --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.ft @@ -0,0 +1,6 @@ + + + + 1 + true + diff --git a/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.html b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.html new file mode 100644 index 0000000000..7ed3827ddf --- /dev/null +++ b/google-cloud-tools-plugin/resources/fileTemplates/j2ee/AppEngineWeb.xml.html @@ -0,0 +1,13 @@ + + + + + + +
+ + This is a built-in template used to create appengine-web.xml descriptor for Google App Engine application + +
+ + diff --git a/google-cloud-tools-plugin/resources/inspectionDescriptions/AppEngineForbiddenCode.html b/google-cloud-tools-plugin/resources/inspectionDescriptions/AppEngineForbiddenCode.html new file mode 100644 index 0000000000..e392641139 --- /dev/null +++ b/google-cloud-tools-plugin/resources/inspectionDescriptions/AppEngineForbiddenCode.html @@ -0,0 +1,8 @@ + + +This inspection reports any code which is forbidden in Google App Engine applications. +

+ Works for Web facets containing Google App Engine sub-facet + + + diff --git a/google-cloud-tools-plugin/runtime/build.gradle b/google-cloud-tools-plugin/runtime/build.gradle new file mode 100644 index 0000000000..2705c1a8a5 --- /dev/null +++ b/google-cloud-tools-plugin/runtime/build.gradle @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +sourceSets.main.java.srcDirs = ['src'] + +// Overriding paths in root build file since this module is nested one level deeper +pmd { + ruleSetFiles = files('../../custom-pmd-ruleset.xml'); +} +findbugs { + excludeFilter = file('../../findbugs-excludefilter.xml') +} + +jar.archiveName = "appEngine-runtime.jar" diff --git a/google-cloud-tools-plugin/runtime/src/com/intellij/appengine/rt/EnhancerRunner.java b/google-cloud-tools-plugin/runtime/src/com/intellij/appengine/rt/EnhancerRunner.java new file mode 100644 index 0000000000..26603d2890 --- /dev/null +++ b/google-cloud-tools-plugin/runtime/src/com/intellij/appengine/rt/EnhancerRunner.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.rt; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author nik + */ +public class EnhancerRunner { + + @SuppressFBWarnings + public static void main(String[] args) + throws IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, + IllegalAccessException { + File argsFile = new File(args[0]); + List argsList = new ArrayList(); + argsList.addAll(Arrays.asList(args).subList(2, args.length)); + BufferedReader reader + = new BufferedReader(new InputStreamReader( + new FileInputStream(argsFile), StandardCharsets.UTF_8)); + try { + while (reader.ready()) { + final String arg = reader.readLine(); + argsList.add(arg); + } + } finally { + reader.close(); + } + argsFile.delete(); + + String className = args[1]; + final Class delegate = Class.forName(className); + final String[] allArgs = argsList.toArray(new String[argsList.size()]); + delegate.getMethod("main", String[].class).invoke(null, (Object) allArgs); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/converter/AppEngineFacetConverterProvider.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/converter/AppEngineFacetConverterProvider.java new file mode 100644 index 0000000000..7980642249 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/converter/AppEngineFacetConverterProvider.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.converter; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacetType; + +import com.intellij.conversion.CannotConvertException; +import com.intellij.conversion.ConversionContext; +import com.intellij.conversion.ConversionProcessor; +import com.intellij.conversion.ConverterProvider; +import com.intellij.conversion.ModuleSettings; +import com.intellij.conversion.ProjectConverter; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.util.containers.ContainerUtil; + +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.serialization.facet.JpsFacetSerializer; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineFacetConverterProvider extends ConverterProvider { + + public AppEngineFacetConverterProvider() { + super("google-app-engine-facet"); + } + + @NotNull + @Override + public ProjectConverter createConverter(@NotNull ConversionContext context) { + return new ProjectConverter() { + @Nullable + @Override + public ConversionProcessor createModuleFileConverter() { + return new GoogleAppEngineFacetConversionProcessor(); + } + }; + } + + @NotNull + @Override + public String getConversionDescription() { + return "Google App Engine facets will be decoupled from Web facets"; + } + + private static class GoogleAppEngineFacetConversionProcessor extends + ConversionProcessor { + + @Override + public boolean isConversionNeeded(ModuleSettings settings) { + return !getAppEngineFacetTags(settings).isEmpty(); + } + + @Override + public void process(ModuleSettings settings) throws CannotConvertException { + List facetTags = getAppEngineFacetTags(settings); + for (Element tag : facetTags) { + tag.detach(); + } + Element facetTag = ContainerUtil.getFirstItem(facetTags); + if (facetTag != null) { + String facetName = facetTag.getAttributeValue(JpsFacetSerializer.NAME_ATTRIBUTE); + Element configuration = facetTag.getChild(JpsFacetSerializer.CONFIGURATION_TAG); + settings.addFacetElement(AppEngineFacetType.STRING_ID, facetName, + (Element) configuration.clone()); + } + } + + @NotNull + private static List getAppEngineFacetTags(@NotNull ModuleSettings settings) { + List appEngineFacetTags = new ArrayList(); + for (Element webFacetTag : settings.getFacetElements("web")) { + for (Element childFacetTag : JDOMUtil + .getChildren(webFacetTag, JpsFacetSerializer.FACET_TAG)) { + if (AppEngineFacetType.STRING_ID + .equals(childFacetTag.getAttributeValue(JpsFacetSerializer.TYPE_ATTRIBUTE))) { + appEngineFacetTags.add(childFacetTag); + } + } + } + return appEngineFacetTags; + } + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/descriptor/AppEngineWebSchemaProvider.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/descriptor/AppEngineWebSchemaProvider.java new file mode 100644 index 0000000000..68cbf4252b --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/descriptor/AppEngineWebSchemaProvider.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.descriptor; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiPackage; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.xml.XmlSchemaProvider; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * @author nik + */ +public class AppEngineWebSchemaProvider extends XmlSchemaProvider { + + private static final Set FILE_NAMES = new HashSet( + Arrays.asList(AppEngineUtilLegacy.APP_ENGINE_WEB_XML_NAME, + AppEngineUtilLegacy.APP_ENGINE_APPLICATION_XML_NAME, + AppEngineUtilLegacy.JDO_CONFIG_XML_NAME)); + + @Override + public boolean isAvailable(@NotNull XmlFile file) { + if (!FILE_NAMES.contains(file.getName())) { + return false; + } + final Module module = ModuleUtilCore.findModuleForPsiElement(file); + return AppEngineFacet.getAppEngineFacetByModule(module) != null; + } + + @Override + public XmlFile getSchema(@NotNull @NonNls String url, @Nullable Module module, + @NotNull PsiFile baseFile) { + if (module == null) { + return null; + } + + if (url.startsWith("http://appengine.google.com/ns/")) { + AppEngineFacet facet = AppEngineFacet.getAppEngineFacetByModule(module); + if (facet != null) { + final File file; + if (isApplicationXmlFile(baseFile)) { + file = facet.getSdk().getApplicationSchemeFile(); + } else { + file = facet.getSdk().getWebSchemeFile(); + } + final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file); + if (virtualFile != null) { + final PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(virtualFile); + if (psiFile instanceof XmlFile) { + return (XmlFile) psiFile; + } + } + } + } else if (url.startsWith("http://java.sun.com/xml/ns/jdo/jdoconfig")) { + final PsiPackage jdoPackage = JavaPsiFacade.getInstance(module.getProject()) + .findPackage("javax.jdo"); + final GlobalSearchScope scope = GlobalSearchScope + .moduleWithDependenciesAndLibrariesScope(module); + if (jdoPackage != null) { + for (PsiDirectory directory : jdoPackage.getDirectories(scope)) { + final PsiFile file = directory.findFile("jdoconfig_2_3.xsd"); + if (file instanceof XmlFile) { + return (XmlFile) file; + } + } + } + } + + return null; + } + + private static boolean isApplicationXmlFile(PsiFile baseFile) { + if (!(baseFile instanceof XmlFile)) { + return false; + } + + XmlTag rootTag = ((XmlFile) baseFile).getRootTag(); + return rootTag != null && rootTag.getName().equals("appengine-application"); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacet.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacet.java new file mode 100644 index 0000000000..b30f14e650 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacet.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdkManager; + +import com.intellij.facet.Facet; +import com.intellij.facet.FacetManager; +import com.intellij.facet.FacetType; +import com.intellij.facet.FacetTypeId; +import com.intellij.facet.FacetTypeRegistry; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlFile; +import com.intellij.util.ReflectionUtil; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomManager; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author nik + */ +public class AppEngineFacet extends Facet { + + public static final FacetTypeId ID = new FacetTypeId("appEngine"); + + public AppEngineFacet(@NotNull FacetType facetType, + @NotNull Module module, + @NotNull String name, + @NotNull AppEngineFacetConfiguration configuration) { + super(facetType, module, name, configuration, null); + } + + public static FacetType getFacetType() { + return FacetTypeRegistry.getInstance().findFacetType(ID); + } + + @Nullable + public static AppEngineFacet getAppEngineFacetByModule(@Nullable Module module) { + if (module == null) { + return null; + } + return FacetManager.getInstance(module).getFacetByType(ID); + } + + @NotNull + public AppEngineSdk getSdk() { + return AppEngineSdkManager.getInstance().findSdk(getConfiguration().getSdkHomePath()); + } + + //todo[nik] copied from JamCommonUtil + @Nullable + private static T getRootElement(final PsiFile file, final Class domClass, + final Module module) { + if (!(file instanceof XmlFile)) { + return null; + } + final DomManager domManager = DomManager.getDomManager(file.getProject()); + final DomFileElement element = domManager + .getFileElement((XmlFile) file, DomElement.class); + if (element == null) { + return null; + } + final DomElement root = element.getRootElement(); + if (!ReflectionUtil.isAssignable(domClass, root.getClass())) { + return null; + } + return (T) root; + } + + + public boolean shouldRunEnhancerFor(@NotNull VirtualFile file) { + for (String path : getConfiguration().getFilesToEnhance()) { + final VirtualFile toEnhance = LocalFileSystem.getInstance().findFileByPath(path); + if (toEnhance != null && VfsUtilCore.isAncestor(toEnhance, file, false)) { + return true; + } + } + return false; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetConfiguration.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetConfiguration.java new file mode 100644 index 0000000000..3b3c3c3951 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetConfiguration.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.intellij.facet.FacetConfiguration; +import com.intellij.facet.ui.FacetEditorContext; +import com.intellij.facet.ui.FacetEditorTab; +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; + +import org.jdom.Element; +import org.jetbrains.jps.appengine.model.PersistenceApi; +import org.jetbrains.jps.appengine.model.impl.AppEngineModuleExtensionProperties; + +import java.util.List; + +/** + * @author nik + */ +public class AppEngineFacetConfiguration implements FacetConfiguration, + PersistentStateComponent { + + private AppEngineModuleExtensionProperties myProperties + = new AppEngineModuleExtensionProperties(); + + public FacetEditorTab[] createEditorTabs(FacetEditorContext editorContext, + FacetValidatorsManager validatorsManager) { + return new FacetEditorTab[]{ + new AppEngineFacetEditor(this, editorContext, validatorsManager) + }; + } + + public void readExternal(Element element) throws InvalidDataException { + } + + public void writeExternal(Element element) throws WriteExternalException { + } + + public AppEngineModuleExtensionProperties getState() { + return myProperties; + } + + public void loadState(AppEngineModuleExtensionProperties state) { + myProperties = state; + } + + public String getSdkHomePath() { + return myProperties.mySdkHomePath; + } + + public boolean isRunEnhancerOnMake() { + return myProperties.myRunEnhancerOnMake; + } + + public List getFilesToEnhance() { + return myProperties.myFilesToEnhance; + } + + public PersistenceApi getPersistenceApi() { + return myProperties.myPersistenceApi; + } + + public void setSdkHomePath(String sdkHomePath) { + myProperties.mySdkHomePath = sdkHomePath; + } + + public void setPersistenceApi(PersistenceApi persistenceApi) { + myProperties.myPersistenceApi = persistenceApi; + } + + public void setFilesToEnhance(List filesToEnhance) { + myProperties.myFilesToEnhance = filesToEnhance; + } + + public void setRunEnhancerOnMake(boolean runEnhancerOnMake) { + myProperties.myRunEnhancerOnMake = runEnhancerOnMake; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.form b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.form new file mode 100644 index 0000000000..2d9e64ea44 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.form @@ -0,0 +1,86 @@ + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.java new file mode 100644 index 0000000000..5feb8d59a1 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetEditor.java @@ -0,0 +1,224 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.impl.AppEngineSdkUtil; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.facet.Facet; +import com.intellij.facet.ui.FacetEditorContext; +import com.intellij.facet.ui.FacetEditorTab; +import com.intellij.facet.ui.FacetEditorValidator; +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.ide.presentation.VirtualFilePresentation; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.AnActionButtonRunnable; +import com.intellij.ui.GuiUtils; +import com.intellij.ui.JBColor; +import com.intellij.ui.RightAlignedLabelUI; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.components.JBList; +import com.intellij.util.PlatformIcons; + +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JPanel; + +/** + * @author nik + */ +public class AppEngineFacetEditor extends FacetEditorTab { + + private final AppEngineFacetConfiguration myFacetConfiguration; + private final FacetEditorContext myContext; + private JPanel myMainPanel; + private JPanel mySdkEditorPanel; + private JCheckBox myRunEnhancerOnMakeCheckBox; + private JPanel myFilesToEnhancePanel; + private JList myFilesList; + private JComboBox myPersistenceApiComboBox; + private JPanel myFilesPanel; + private AppEngineSdkEditor mySdkEditor; + private DefaultListModel myFilesListModel; + + public AppEngineFacetEditor(AppEngineFacetConfiguration facetConfiguration, + FacetEditorContext context, FacetValidatorsManager validatorsManager) { + myFacetConfiguration = facetConfiguration; + myContext = context; + mySdkEditor = new AppEngineSdkEditor(myContext.getProject()); + validatorsManager.registerValidator(new FacetEditorValidator() { + @NotNull + @Override + public ValidationResult check() { + return AppEngineSdkUtil.checkPath(mySdkEditor.getPath()); + } + }, mySdkEditor.getComboBox()); + + myRunEnhancerOnMakeCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + GuiUtils.enableChildren(myRunEnhancerOnMakeCheckBox.isSelected(), myFilesToEnhancePanel); + if (myRunEnhancerOnMakeCheckBox.isSelected() && myFilesListModel.isEmpty()) { + fillFilesList( + AppEngineUtilLegacy.getDefaultSourceRootsToEnhance(myContext.getRootModel())); + } + } + }); + + myFilesListModel = new DefaultListModel(); + myFilesList = new JBList(myFilesListModel); + myFilesList.setCellRenderer(new FilesListCellRenderer()); + myFilesPanel.add(ToolbarDecorator.createDecorator(myFilesList) + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + doAdd(); + } + }).disableUpDownActions().createPanel(), BorderLayout.CENTER); + + PersistenceApiComboboxUtil.setComboboxModel(myPersistenceApiComboBox, false); + } + + private void doAdd() { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, false, false, + false, true); + final ModuleRootModel rootModel = myContext.getRootModel(); + descriptor.setRoots(rootModel.getSourceRoots(JavaModuleSourceRootTypes.SOURCES)); + final VirtualFile[] files = FileChooser.chooseFiles(descriptor, myContext.getProject(), null); + for (VirtualFile file : files) { + myFilesListModel.addElement(file.getPath()); + } + } + + @Nls + public String getDisplayName() { + return "Google App Engine"; + } + + @NotNull + public JComponent createComponent() { + mySdkEditorPanel.add(BorderLayout.CENTER, mySdkEditor.getMainComponent()); + return myMainPanel; + } + + @Override + public boolean isModified() { + return myRunEnhancerOnMakeCheckBox.isSelected() != myFacetConfiguration.isRunEnhancerOnMake() + || !mySdkEditor.getPath().equals(myFacetConfiguration.getSdkHomePath()) + || !getConfiguredFiles().equals(myFacetConfiguration.getFilesToEnhance()) + || PersistenceApiComboboxUtil.getSelectedApi(myPersistenceApiComboBox) + != myFacetConfiguration.getPersistenceApi(); + } + + private List getConfiguredFiles() { + final List files = new ArrayList(); + for (int i = 0; i < myFilesListModel.getSize(); i++) { + files.add((String) myFilesListModel.getElementAt(i)); + } + return files; + } + + @Override + public void apply() { + myFacetConfiguration.setSdkHomePath(mySdkEditor.getPath()); + myFacetConfiguration.setRunEnhancerOnMake(myRunEnhancerOnMakeCheckBox.isSelected()); + myFacetConfiguration.setFilesToEnhance(getConfiguredFiles()); + myFacetConfiguration + .setPersistenceApi(PersistenceApiComboboxUtil.getSelectedApi(myPersistenceApiComboBox)); + } + + @Override + public void reset() { + mySdkEditor.setPath(myFacetConfiguration.getSdkHomePath()); + if (myContext.isNewFacet() && myFacetConfiguration.getSdkHomePath().length() == 0) { + mySdkEditor.setDefaultPath(); + } + myFilesListModel.removeAllElements(); + fillFilesList(myFacetConfiguration.getFilesToEnhance()); + myRunEnhancerOnMakeCheckBox.setSelected(myFacetConfiguration.isRunEnhancerOnMake()); + myPersistenceApiComboBox + .setSelectedItem(myFacetConfiguration.getPersistenceApi().getDisplayName()); + } + + private void fillFilesList(final List paths) { + for (String path : paths) { + myFilesListModel.addElement(path); + } + } + + @SuppressWarnings("checkstyle:abbreviationaswordinname") + public void disposeUIResources() { + } + + @Override + public String getHelpTopic() { + return "Google_App_Engine_Facet"; + } + + @Override + public void onFacetInitialized(@NotNull Facet facet) { + AppEngineWebIntegration.getInstance().setupDevServer(((AppEngineFacet) facet).getSdk()); + } + + private class FilesListCellRenderer extends DefaultListCellRenderer { + + private FilesListCellRenderer() { + setUI(new RightAlignedLabelUI()); + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + final Component rendererComponent = super + .getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof String) { + final String path = (String) value; + final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); + if (file == null) { + setForeground(JBColor.RED); + setIcon(null); + } else { + setForeground(myFilesList.getForeground()); + setIcon(file.isDirectory() ? PlatformIcons.FOLDER_ICON + : VirtualFilePresentation.getIcon(file)); + } + setText(path); + } + return rendererComponent; + } + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetType.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetType.java new file mode 100644 index 0000000000..b32373bd38 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFacetType.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.facet.Facet; +import com.intellij.facet.FacetType; +import com.intellij.openapi.module.JavaModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Icon; + +/** + * @author nik + */ +public class AppEngineFacetType extends FacetType { + public static final String STRING_ID = "google-app-engine"; + + public AppEngineFacetType() { + super(AppEngineFacet.ID, STRING_ID, "Google App Engine"); + } + + public AppEngineFacetConfiguration createDefaultConfiguration() { + return new AppEngineFacetConfiguration(); + } + + public AppEngineFacet createFacet(@NotNull Module module, + String name, + @NotNull AppEngineFacetConfiguration configuration, + @Nullable Facet underlyingFacet) { + return new AppEngineFacet(this, module, name, configuration); + } + + public boolean isSuitableModuleType(ModuleType moduleType) { + return moduleType instanceof JavaModuleType; + } + + @NotNull + @Override + public String getDefaultFacetName() { + return "Google App Engine"; + } + + @Override + public Icon getIcon() { + return GoogleCloudToolsIcons.APP_ENGINE; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkDetector.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkDetector.java new file mode 100644 index 0000000000..66d2fda269 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkDetector.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdkManager; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.facet.FacetType; +import com.intellij.framework.FrameworkType; +import com.intellij.framework.detection.FacetBasedFrameworkDetector; +import com.intellij.framework.detection.FileContentPattern; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.patterns.ElementPattern; +import com.intellij.util.indexing.FileContent; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author nik + */ +public class AppEngineFrameworkDetector extends + FacetBasedFrameworkDetector { + + public AppEngineFrameworkDetector() { + super("appengine-java"); + } + + @Override + public FacetType getFacetType() { + return FacetType.findInstance(AppEngineFacetType.class); + } + + @Override + public FrameworkType getFrameworkType() { + return AppEngineFrameworkType.getFrameworkType(); + } + + @Override + public void setupFacet(@NotNull AppEngineFacet facet, ModifiableRootModel model) { + final List sdks = AppEngineSdkManager.getInstance().getValidSdks(); + if (!sdks.isEmpty()) { + facet.getConfiguration().setSdkHomePath(sdks.get(0).getSdkHomePath()); + } + } + + @NotNull + @Override + public FileType getFileType() { + return StdFileTypes.XML; + } + + @NotNull + @Override + public ElementPattern createSuitableFilePattern() { + return FileContentPattern.fileContent().withName(AppEngineUtilLegacy.APP_ENGINE_WEB_XML_NAME) + .xmlWithRootTag("appengine-web-app"); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkType.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkType.java new file mode 100644 index 0000000000..baa6680363 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineFrameworkType.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.framework.FrameworkTypeEx; +import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.Icon; + +/** + * @author nik + */ +public class AppEngineFrameworkType extends FrameworkTypeEx { + public static final String ID = "appengine-java"; + + public AppEngineFrameworkType() { + super(ID); + } + + static AppEngineFrameworkType getFrameworkType() { + return EP_NAME.findExtension(AppEngineFrameworkType.class); + } + + @NotNull + @Override + public FrameworkSupportInModuleProvider createProvider() { + return new AppEngineSupportProvider(); + } + + @NotNull + @Override + public String getPresentableName() { + return "Google App Engine"; + } + + @NotNull + @Override + public Icon getIcon() { + return GoogleCloudToolsIcons.APP_ENGINE; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSdkEditor.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSdkEditor.java new file mode 100644 index 0000000000..d414784db5 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSdkEditor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdkManager; + +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.ComponentWithBrowseButton; +import com.intellij.openapi.ui.TextComponentAccessor; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.ui.ComboboxWithBrowseButton; + +import org.jetbrains.annotations.Nullable; + +import javax.swing.JComboBox; +import javax.swing.JPanel; + +/** + * @author nik + */ +public class AppEngineSdkEditor { + + private ComboboxWithBrowseButton myPathEditor; + + public AppEngineSdkEditor(final @Nullable Project project) { + myPathEditor = new ComboboxWithBrowseButton(new ComboBox(100)); + myPathEditor.addBrowseFolderListener(project, + new ComponentWithBrowseButton.BrowseFolderActionListener("Google App Engine SDK", + "Specify Google App Engine Java SDK home", + myPathEditor, project, + FileChooserDescriptorFactory.createSingleFolderDescriptor(), + TextComponentAccessor.STRING_COMBOBOX_WHOLE_TEXT)); + final JComboBox comboBox = myPathEditor.getComboBox(); + comboBox.setEditable(true); + comboBox.removeAllItems(); + for (AppEngineSdk sdk : AppEngineSdkManager.getInstance().getValidSdks()) { + comboBox.addItem(FileUtil.toSystemDependentName(sdk.getSdkHomePath())); + } + } + + + public JPanel getMainComponent() { + return myPathEditor; + } + + public String getPath() { + return FileUtil + .toSystemIndependentName((String) myPathEditor.getComboBox().getEditor().getItem()); + } + + public void setPath(final String path) { + myPathEditor.getComboBox().setSelectedItem(FileUtil.toSystemDependentName(path)); + } + + public void setDefaultPath() { + final JComboBox comboBox = myPathEditor.getComboBox(); + if (comboBox.getItemCount() > 0) { + comboBox.setSelectedIndex(0); + } + } + + public JComboBox getComboBox() { + return myPathEditor.getComboBox(); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportConfigurable.form b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportConfigurable.form new file mode 100644 index 0000000000..abe74988a2 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportConfigurable.form @@ -0,0 +1,60 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportProvider.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportProvider.java new file mode 100644 index 0000000000..2905107463 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineSupportProvider.java @@ -0,0 +1,359 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.impl.AppEngineSdkUtil; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.facet.FacetManager; +import com.intellij.facet.FacetType; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.framework.FrameworkTypeEx; +import com.intellij.framework.addSupport.FrameworkSupportInModuleConfigurable; +import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; +import com.intellij.icons.AllIcons; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel; +import com.intellij.ide.util.frameworkSupport.FrameworkSupportModelListener; +import com.intellij.ide.util.frameworkSupport.FrameworkSupportProvider; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.JavaModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModifiableModelsProvider; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.roots.ui.configuration.FacetsProvider; +import com.intellij.openapi.ui.LabeledComponent; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.packaging.elements.ArtifactRootElement; +import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.packaging.impl.artifacts.ArtifactUtil; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.HyperlinkLabel; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.jps.appengine.model.PersistenceApi; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.text.JTextComponent; + +/** + * @author nik + */ +public class AppEngineSupportProvider extends FrameworkSupportInModuleProvider { + + private static final Logger LOG = Logger + .getInstance("#com.intellij.appengine.facet.AppEngineSupportProvider"); + public static final String JPA_FRAMEWORK_ID = "facet:jpa"; + + @NotNull + @Override + public FrameworkTypeEx getFrameworkType() { + return AppEngineFrameworkType.getFrameworkType(); + } + + @Override + public List getDependenciesFrameworkIds() { + return AppEngineWebIntegration.getInstance().getAppEngineFrameworkDependencies(); + } + + @Override + public boolean isEnabledForModuleType(@NotNull ModuleType moduleType) { + return moduleType instanceof JavaModuleType; + } + + @Override + public boolean isSupportAlreadyAdded(@NotNull Module module, + @NotNull FacetsProvider facetsProvider) { + return !facetsProvider.getFacetsByType(module, AppEngineFacet.ID).isEmpty(); + } + + @Nullable + public static VirtualFile createFileFromTemplate(final String templateName, + final VirtualFile parent, final String fileName) { + parent.refresh(false, false); + final FileTemplate template = FileTemplateManager.getDefaultInstance() + .getJ2eeTemplate(templateName); + try { + final String text = template + .getText(FileTemplateManager.getDefaultInstance().getDefaultProperties()); + VirtualFile file = parent.findChild(fileName); + if (file == null) { + file = parent.createChildData(AppEngineSupportProvider.class, fileName); + } + VfsUtil.saveText(file, text); + return file; + } catch (IOException ioe) { + LOG.error(ioe); + return null; + } + } + + private void addSupport(final Module module, + final ModifiableRootModel rootModel, + FrameworkSupportModel frameworkSupportModel, + String sdkPath, + @Nullable PersistenceApi persistenceApi) { + FacetType facetType = AppEngineFacet + .getFacetType(); + AppEngineFacet appEngineFacet = FacetManager.getInstance(module) + .addFacet(facetType, facetType.getDefaultFacetName(), null); + AppEngineWebIntegration webIntegration = AppEngineWebIntegration.getInstance(); + webIntegration.registerFrameworkInModel(frameworkSupportModel, appEngineFacet); + final AppEngineFacetConfiguration facetConfiguration = appEngineFacet.getConfiguration(); + facetConfiguration.setSdkHomePath(sdkPath); + final AppEngineSdk sdk = appEngineFacet.getSdk(); + final Artifact webArtifact = findOrCreateWebArtifact(appEngineFacet); + + final VirtualFile webDescriptorDir = webIntegration + .suggestParentDirectoryForAppEngineWebXml(module, rootModel); + if (webDescriptorDir != null) { + VirtualFile descriptor = createFileFromTemplate( + AppEngineTemplateGroupDescriptorFactory.APP_ENGINE_WEB_XML_TEMPLATE, webDescriptorDir, + AppEngineUtilLegacy.APP_ENGINE_WEB_XML_NAME); + if (descriptor != null) { + webIntegration.addDescriptor(webArtifact, module.getProject(), descriptor); + } + } + + final Project project = module.getProject(); + webIntegration.addDevServerToModuleDependencies(rootModel, sdk); + + final Library apiJar = addProjectLibrary(module, "AppEngine API", sdk.getUserLibraryPaths(), + VirtualFile.EMPTY_ARRAY); + rootModel.addLibraryEntry(apiJar); + webIntegration.addLibraryToArtifact(apiJar, webArtifact, project); + + if (persistenceApi != null) { + facetConfiguration.setRunEnhancerOnMake(true); + facetConfiguration.setPersistenceApi(persistenceApi); + facetConfiguration.getFilesToEnhance() + .addAll(AppEngineUtilLegacy.getDefaultSourceRootsToEnhance(rootModel)); + try { + final VirtualFile[] sourceRoots = rootModel.getSourceRoots(); + final VirtualFile sourceRoot; + if (sourceRoots.length > 0) { + sourceRoot = sourceRoots[0]; + } else { + sourceRoot = findOrCreateChildDirectory(rootModel.getContentRoots()[0], "src"); + } + VirtualFile metaInf = findOrCreateChildDirectory(sourceRoot, "META-INF"); + if (persistenceApi == PersistenceApi.JDO || persistenceApi == PersistenceApi.JDO3) { + createFileFromTemplate( + AppEngineTemplateGroupDescriptorFactory.APP_ENGINE_JDO_CONFIG_TEMPLATE, metaInf, + AppEngineUtilLegacy.JDO_CONFIG_XML_NAME); + } else { + final VirtualFile file = createFileFromTemplate( + AppEngineTemplateGroupDescriptorFactory.APP_ENGINE_JPA_CONFIG_TEMPLATE, metaInf, + AppEngineUtilLegacy.JPA_CONFIG_XML_NAME); + if (file != null) { + webIntegration.setupJpaSupport(module, file); + } + } + } catch (IOException ioe) { + LOG.error(ioe); + } + final Library library = addProjectLibrary(module, "AppEngine ORM", + Collections.singletonList(sdk.getOrmLibDirectoryPath()), sdk.getOrmLibSources()); + rootModel.addLibraryEntry(library); + webIntegration.addLibraryToArtifact(library, webArtifact, project); + } + } + + @NotNull + private static Artifact findOrCreateWebArtifact(AppEngineFacet appEngineFacet) { + Module module = appEngineFacet.getModule(); + ArtifactType webArtifactType = AppEngineWebIntegration.getInstance() + .getAppEngineWebArtifactType(); + final Collection artifacts = ArtifactUtil.getArtifactsContainingModuleOutput(module); + for (Artifact artifact : artifacts) { + if (webArtifactType.equals(artifact.getArtifactType())) { + return artifact; + } + } + ArtifactManager artifactManager = ArtifactManager.getInstance(module.getProject()); + PackagingElementFactory elementFactory = PackagingElementFactory.getInstance(); + ArtifactRootElement root = elementFactory.createArtifactRootElement(); + elementFactory.getOrCreateDirectory(root, "WEB-INF/classes") + .addOrFindChild(elementFactory.createModuleOutput(module)); + return artifactManager.addArtifact(module.getName(), webArtifactType, root); + } + + private static Library addProjectLibrary(final Module module, final String name, + final List jarDirectories, final VirtualFile[] sources) { + return new WriteAction() { + protected void run(@NotNull final Result result) { + final LibraryTable libraryTable = LibraryTablesRegistrar.getInstance() + .getLibraryTable(module.getProject()); + Library library = libraryTable.getLibraryByName(name); + if (library == null) { + library = libraryTable.createLibrary(name); + final Library.ModifiableModel model = library.getModifiableModel(); + for (String path : jarDirectories) { + String url = VfsUtilCore.pathToUrl(path); + VirtualFileManager.getInstance().refreshAndFindFileByUrl(url); + model.addJarDirectory(url, false); + } + for (VirtualFile sourceRoot : sources) { + model.addRoot(sourceRoot, OrderRootType.SOURCES); + } + model.commit(); + } + result.setResult(library); + } + }.execute().getResultObject(); + } + + private VirtualFile findOrCreateChildDirectory(VirtualFile parent, final String name) + throws IOException { + VirtualFile child = parent.findChild(name); + if (child != null) { + return child; + } + return parent.createChildDirectory(this, name); + } + + @NotNull + @Override + public FrameworkSupportInModuleConfigurable createConfigurable( + @NotNull FrameworkSupportModel model) { + return new AppEngineSupportConfigurable(model); + } + + @TestOnly + public static void setSdkPath(FrameworkSupportInModuleConfigurable configurable, String path) { + ((AppEngineSupportConfigurable) configurable).mySdkEditor.setPath(path); + } + + private class AppEngineSupportConfigurable extends FrameworkSupportInModuleConfigurable implements + FrameworkSupportModelListener { + + private final FrameworkSupportModel myFrameworkSupportModel; + private JPanel myMainPanel; + private final AppEngineSdkEditor mySdkEditor; + private JComboBox myPersistenceApiComboBox; + private JPanel mySdkPanel; + private final HyperlinkLabel myErrorLabel; + private JPanel myErrorPanel; + + private AppEngineSupportConfigurable(FrameworkSupportModel model) { + myFrameworkSupportModel = model; + mySdkEditor = new AppEngineSdkEditor(model.getProject()); + mySdkPanel + .add(LabeledComponent.create(mySdkEditor.getMainComponent(), "Google App Engine SDK:"), + BorderLayout.CENTER); + PersistenceApiComboboxUtil.setComboboxModel(myPersistenceApiComboBox, true); + if (model.isFrameworkSelected(JPA_FRAMEWORK_ID)) { + myPersistenceApiComboBox.setSelectedItem(PersistenceApi.JPA.getDisplayName()); + } + model.addFrameworkListener(this); + + myErrorLabel = new HyperlinkLabel(); + myErrorLabel.setIcon(AllIcons.RunConfigurations.ConfigurationWarning); + myErrorLabel.setVisible(false); + myErrorLabel.setHyperlinkTarget(AppEngineSdkUtil.APP_ENGINE_DOWNLOAD_URL); + myErrorPanel.add(BorderLayout.CENTER, myErrorLabel); + + final Component component = mySdkEditor.getComboBox().getEditor().getEditorComponent(); + if (component instanceof JTextComponent) { + ((JTextComponent) component).getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent event) { + checkSdk(); + } + }); + } + checkSdk(); + } + + private void checkSdk() { + final String path = mySdkEditor.getPath(); + if (StringUtil.isEmptyOrSpaces(path)) { + myErrorLabel.setVisible(true); + myErrorLabel.setHyperlinkText("App Engine SDK path not specified. ", "Download", ""); + myMainPanel.repaint(); + return; + } + + final ValidationResult result = AppEngineSdkUtil.checkPath(path); + myErrorLabel.setVisible(!result.isOk()); + if (!result.isOk()) { + myErrorLabel.setText("App Engine SDK path is not correct"); + } + myMainPanel.repaint(); + } + + public void frameworkSelected(@NotNull FrameworkSupportProvider provider) { + if (provider.getId().equals(JPA_FRAMEWORK_ID)) { + myPersistenceApiComboBox.setSelectedItem(PersistenceApi.JPA.getDisplayName()); + } + } + + public void frameworkUnselected(@NotNull FrameworkSupportProvider provider) { + if (provider.getId().equals(JPA_FRAMEWORK_ID)) { + myPersistenceApiComboBox.setSelectedItem(PersistenceApiComboboxUtil.NONE_ITEM); + } + } + + @Override + public void wizardStepUpdated() { + } + + @Override + public void addSupport(@NotNull Module module, + @NotNull ModifiableRootModel rootModel, + @NotNull ModifiableModelsProvider modifiableModelsProvider) { + AppEngineSupportProvider.this + .addSupport(module, rootModel, myFrameworkSupportModel, mySdkEditor.getPath(), + PersistenceApiComboboxUtil.getSelectedApi(myPersistenceApiComboBox)); + } + + @Nullable + @Override + public JComponent createComponent() { + return myMainPanel; + } + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineTemplateGroupDescriptorFactory.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineTemplateGroupDescriptorFactory.java new file mode 100644 index 0000000000..f865f02c93 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineTemplateGroupDescriptorFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.ide.fileTemplates.FileTemplateDescriptor; +import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptor; +import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptorFactory; +import com.intellij.openapi.fileTypes.StdFileTypes; + +import org.jetbrains.annotations.NonNls; + +/** + * @author nik + */ +public class AppEngineTemplateGroupDescriptorFactory implements FileTemplateGroupDescriptorFactory { + + @NonNls + public static final String APP_ENGINE_WEB_XML_TEMPLATE = "AppEngineWeb.xml"; + @NonNls + public static final String APP_ENGINE_APPLICATION_XML_TEMPLATE = "AppEngineApplication.xml"; + @NonNls + public static final String APP_ENGINE_JDO_CONFIG_TEMPLATE = "AppEngineJdoConfig.xml"; + @NonNls + public static final String APP_ENGINE_JPA_CONFIG_TEMPLATE = "AppEngineJpaConfig.xml"; + + public FileTemplateGroupDescriptor getFileTemplatesDescriptor() { + final FileTemplateDescriptor appEngineXml = new FileTemplateDescriptor( + APP_ENGINE_WEB_XML_TEMPLATE, StdFileTypes.XML.getIcon()); + final FileTemplateDescriptor appEngineApplicationXml = new FileTemplateDescriptor( + APP_ENGINE_APPLICATION_XML_TEMPLATE, StdFileTypes.XML.getIcon()); + final FileTemplateDescriptor jdoConfigXml = new FileTemplateDescriptor( + APP_ENGINE_JDO_CONFIG_TEMPLATE, StdFileTypes.XML.getIcon()); + final FileTemplateDescriptor jpaConfigXml = new FileTemplateDescriptor( + APP_ENGINE_JPA_CONFIG_TEMPLATE, StdFileTypes.XML.getIcon()); + return new FileTemplateGroupDescriptor("Google App Engine", GoogleCloudToolsIcons.APP_ENGINE, + appEngineXml, + appEngineApplicationXml, jdoConfigXml, jpaConfigXml); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineWebIntegration.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineWebIntegration.java new file mode 100644 index 0000000000..8c2b9153e2 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/AppEngineWebIntegration.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; +import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.util.containers.ContainerUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author nik + */ +public abstract class AppEngineWebIntegration { + + public static AppEngineWebIntegration getInstance() { + return ServiceManager.getService(AppEngineWebIntegration.class); + } + + @Nullable + public abstract VirtualFile suggestParentDirectoryForAppEngineWebXml(@NotNull Module module, + @NotNull ModifiableRootModel rootModel); + + @NotNull + public List getAppEngineTargetArtifactTypes() { + return ContainerUtil + .packNullables(getAppEngineWebArtifactType(), getAppEngineApplicationArtifactType()); + } + + @NotNull + public abstract ArtifactType getAppEngineWebArtifactType(); + + @Nullable + public abstract ArtifactType getAppEngineApplicationArtifactType(); + + @NotNull + public abstract List + getAppEngineFrameworkDependencies(); + + public abstract void setupJpaSupport(@NotNull Module module, @NotNull VirtualFile persistenceXml); + + public abstract void setupRunConfiguration(@NotNull AppEngineSdk sdk, @Nullable Artifact artifact, + @NotNull Project project); + + public abstract void setupDevServer(@NotNull AppEngineSdk sdk); + + public abstract void addDevServerToModuleDependencies(@NotNull ModifiableRootModel rootModel, + @NotNull AppEngineSdk sdk); + + public abstract void addLibraryToArtifact(@NotNull Library library, @NotNull Artifact artifact, + @NotNull Project project); + + public abstract List getSdkForConfiguredDevServers(); + + public void addDescriptor(@NotNull Artifact artifact, @NotNull Project project, + @NotNull VirtualFile descriptor) { + } + + public void registerFrameworkInModel(FrameworkSupportModel model, AppEngineFacet appEngineFacet) { + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/PersistenceApiComboboxUtil.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/PersistenceApiComboboxUtil.java new file mode 100644 index 0000000000..bb860f53eb --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/PersistenceApiComboboxUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.appengine.model.PersistenceApi; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; + +/** + * @author nik + */ +public class PersistenceApiComboboxUtil { + @NonNls public static final String NONE_ITEM = "None"; + + private PersistenceApiComboboxUtil() { + } + + public static void setComboboxModel(final JComboBox comboBox, final boolean addNoneItem) { + final DefaultComboBoxModel model = new DefaultComboBoxModel(); + if (addNoneItem) { + model.addElement(NONE_ITEM); + } + for (PersistenceApi api : PersistenceApi.values()) { + model.addElement(api.getDisplayName()); + } + comboBox.setModel(model); + } + + @Nullable + public static PersistenceApi getSelectedApi(final JComboBox comboBox) { + final String apiName = (String)comboBox.getSelectedItem(); + PersistenceApi api = null; + for (PersistenceApi value : PersistenceApi.values()) { + if (value.getDisplayName().equals(apiName)) { + api = value; + } + } + return api; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/impl/AppEngineCommunityWebIntegration.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/impl/AppEngineCommunityWebIntegration.java new file mode 100644 index 0000000000..d53d791b46 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/facet/impl/AppEngineCommunityWebIntegration.java @@ -0,0 +1,129 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.facet.impl; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.packaging.elements.PackagingElement; +import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.packaging.impl.artifacts.PlainArtifactType; +import com.intellij.util.ArrayUtil; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * @author nik. + */ +public class AppEngineCommunityWebIntegration extends AppEngineWebIntegration { + + private static final Logger LOG = Logger.getInstance(AppEngineCommunityWebIntegration.class); + + @Nullable + @Override + public VirtualFile suggestParentDirectoryForAppEngineWebXml(@NotNull Module module, + @NotNull ModifiableRootModel rootModel) { + final VirtualFile root = ArrayUtil.getFirstElement(rootModel.getContentRoots()); + if (root != null) { + try { + return VfsUtil.createDirectoryIfMissing(root, "WEB-INF"); + } catch (IOException ioe) { + LOG.info(ioe); + return null; + } + } + return null; + } + + @NotNull + @Override + public ArtifactType getAppEngineWebArtifactType() { + return PlainArtifactType.getInstance(); + } + + @Nullable + @Override + public ArtifactType getAppEngineApplicationArtifactType() { + return null; + } + + @Override + public void setupJpaSupport(@NotNull Module module, @NotNull VirtualFile persistenceXml) { + } + + @Override + public void setupRunConfiguration(@NotNull AppEngineSdk sdk, + @Nullable Artifact artifact, + @NotNull Project project) { + } + + @Override + public void setupDevServer(@NotNull AppEngineSdk sdk) { + } + + @Override + public void addDevServerToModuleDependencies(@NotNull ModifiableRootModel rootModel, + @NotNull AppEngineSdk sdk) { + } + + @Override + public void addLibraryToArtifact(@NotNull Library library, @NotNull Artifact artifact, + @NotNull Project project) { + final ArtifactManager artifactManager = ArtifactManager.getInstance(project); + for (PackagingElement element : PackagingElementFactory.getInstance() + .createLibraryElements(library)) { + final String dir = element.getFilesKind(artifactManager.getResolvingContext()) + .containsDirectoriesWithClasses() ? "classes" : "lib"; + artifactManager.addElementsToDirectory(artifact, "WEB-INF/" + dir, element); + } + } + + @Override + public List getSdkForConfiguredDevServers() { + return Collections.emptyList(); + } + + @Override + public void addDescriptor(@NotNull Artifact artifact, @NotNull Project project, + @NotNull VirtualFile descriptor) { + ArtifactManager.getInstance(project).addElementsToDirectory(artifact, "WEB-INF", + PackagingElementFactory.getInstance().createFileCopy(descriptor.getPath(), null)); + } + + @Override + @NotNull + public List + getAppEngineFrameworkDependencies() { + return Collections.emptyList(); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeHandler.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeHandler.java new file mode 100644 index 0000000000..34960ab9c3 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.inspections; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public abstract class AppEngineForbiddenCodeHandler { + public static final ExtensionPointName EP_NAME + = ExtensionPointName.create("com.intellij.appengine.forbiddenCodeHandler"); + + public abstract boolean isNativeMethodAllowed(@NotNull PsiMethod method); +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeInspection.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeInspection.java new file mode 100644 index 0000000000..dd13e1a1a8 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/inspections/AppEngineForbiddenCodeInspection.java @@ -0,0 +1,207 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.inspections; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.InspectionManager; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.JdkOrderEntry; +import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaRecursiveElementWalkingVisitor; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiModifierList; +import com.intellij.psi.PsiNewExpression; +import com.intellij.psi.PsiReferenceExpression; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.util.ClassUtil; + +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineForbiddenCodeInspection extends BaseJavaLocalInspectionTool { + + @Override + public ProblemDescriptor[] checkFile(@NotNull PsiFile file, + @NotNull final InspectionManager manager, final boolean isOnTheFly) { + final Project project = manager.getProject(); + Module module = ModuleUtilCore.findModuleForPsiElement(file); + final AppEngineFacet appEngineFacet = AppEngineFacet.getAppEngineFacetByModule(module); + if (appEngineFacet == null) { + return null; + } + final AppEngineSdk appEngineSdk = appEngineFacet.getSdk(); + if (!appEngineSdk.isValid()) { + return null; + } + + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + final List problems = new ArrayList(); + file.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitDocComment(PsiDocComment comment) { + } + + @Override + public void visitMethod(PsiMethod method) { + final PsiModifierList modifierList = method.getModifierList(); + if (modifierList.hasModifierProperty(PsiModifier.NATIVE)) { + if (!isNativeMethodAllowed(method)) { + problems.add(manager.createProblemDescriptor(modifierList, + "Native methods aren't allowed in App Engine application", isOnTheFly, + LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); + } + } + super.visitMethod(method); + } + + @Override + public void visitNewExpression(PsiNewExpression expression) { + final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); + if (classReference != null) { + final PsiElement resolved = classReference.resolve(); + if (resolved instanceof PsiClass) { + final String qualifiedName = ((PsiClass) resolved).getQualifiedName(); + if (qualifiedName != null && appEngineSdk.isMethodInBlacklist(qualifiedName, "new")) { + final String message = + "App Engine application should not create new instances of '" + qualifiedName + + "' class"; + problems.add(manager.createProblemDescriptor(classReference, message, isOnTheFly, + LocalQuickFix.EMPTY_ARRAY, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); + } + } + } + super.visitNewExpression(expression); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final PsiElement element = methodExpression.resolve(); + if (element instanceof PsiMethod) { + final PsiMethod method = (PsiMethod) element; + final PsiClass psiClass = method.getContainingClass(); + if (psiClass != null) { + final String qualifiedName = psiClass.getQualifiedName(); + final String methodName = method.getName(); + if (qualifiedName != null && appEngineSdk + .isMethodInBlacklist(qualifiedName, methodName)) { + final String message = + "AppEngine application should not call '" + StringUtil.getShortName(qualifiedName) + + "" + + methodName + "' method"; + problems.add(manager.createProblemDescriptor(methodExpression, message, isOnTheFly, + LocalQuickFix.EMPTY_ARRAY, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); + } + } + } + super.visitMethodCallExpression(expression); + } + + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + final PsiElement resolved = reference.resolve(); + if (resolved instanceof PsiClass) { + final PsiFile psiFile = resolved.getContainingFile(); + if (psiFile != null) { + final VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile != null && !fileIndex.isInSource(virtualFile)) { + final List list = fileIndex.getOrderEntriesForFile(virtualFile); + for (OrderEntry entry : list) { + if (entry instanceof JdkOrderEntry) { + final String className = ClassUtil.getJVMClassName((PsiClass) resolved); + if (className != null && !appEngineSdk.isClassInWhiteList(className)) { + problems.add(manager.createProblemDescriptor(reference, + "Class '" + className + "' is not included in App Engine JRE White List", + isOnTheFly, LocalQuickFix.EMPTY_ARRAY, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); + } + } + } + } + } + } + super.visitReferenceElement(reference); + } + }); + return problems.toArray(new ProblemDescriptor[problems.size()]); + } + + private static boolean isNativeMethodAllowed(PsiMethod method) { + for (AppEngineForbiddenCodeHandler handler : AppEngineForbiddenCodeHandler.EP_NAME + .getExtensions()) { + if (handler.isNativeMethodAllowed(method)) { + return true; + } + } + return false; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @NotNull + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; + } + + @Nls + @NotNull + public String getGroupDisplayName() { + return "Google App Engine"; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Forbidden code in App Engine applications"; + } + + @NotNull + public String getShortName() { + return "AppEngineForbiddenCode"; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/maven/AppEngineFacetImporter.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/maven/AppEngineFacetImporter.java new file mode 100644 index 0000000000..712cc5a657 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/maven/AppEngineFacetImporter.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.maven; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacetConfiguration; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacetType; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; +import com.google.cloud.tools.intellij.appengine.sdk.impl.AppEngineSdkUtil; + +import com.intellij.facet.FacetType; +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.util.io.ZipUtil; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.maven.importing.FacetImporter; +import org.jetbrains.idea.maven.importing.MavenRootModelAdapter; +import org.jetbrains.idea.maven.model.MavenArtifact; +import org.jetbrains.idea.maven.model.MavenArtifactInfo; +import org.jetbrains.idea.maven.model.MavenPlugin; +import org.jetbrains.idea.maven.model.MavenRemoteRepository; +import org.jetbrains.idea.maven.project.MavenProject; +import org.jetbrains.idea.maven.project.MavenProjectChanges; +import org.jetbrains.idea.maven.project.MavenProjectsProcessorTask; +import org.jetbrains.idea.maven.project.MavenProjectsTree; +import org.jetbrains.idea.maven.project.ResolveContext; +import org.jetbrains.idea.maven.server.MavenEmbedderWrapper; +import org.jetbrains.idea.maven.server.NativeMavenProjectHolder; +import org.jetbrains.idea.maven.utils.MavenLog; +import org.jetbrains.idea.maven.utils.MavenProcessCanceledException; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * @author nik + */ +public class AppEngineFacetImporter extends + FacetImporter { + + public AppEngineFacetImporter() { + super("com.google.appengine", "appengine-maven-plugin", + FacetType.findInstance(AppEngineFacetType.class)); + } + + @Override + public void resolve(Project project, + MavenProject mavenProject, + NativeMavenProjectHolder nativeMavenProject, + MavenEmbedderWrapper embedder, + ResolveContext context) throws MavenProcessCanceledException { + String version = getVersion(mavenProject); + if (version != null) { + List repos = mavenProject.getRemoteRepositories(); + MavenArtifactInfo artifactInfo = new MavenArtifactInfo("com.google.appengine", + "appengine-java-sdk", version, "zip", null); + MavenArtifact artifact = embedder.resolve(artifactInfo, repos); + File file = artifact.getFile(); + File unpackedSdkPath = new File(file.getParentFile(), "appengine-java-sdk"); + if (file.exists() && !AppEngineSdkUtil + .checkPath(FileUtil.toSystemIndependentName(unpackedSdkPath.getAbsolutePath())).isOk()) { + try { + ZipUtil.extract(file, unpackedSdkPath, null, false); + } catch (IOException ioe) { + MavenLog.LOG.warn("cannot unpack AppEngine SDK", ioe); + } + } + } + } + + @Nullable + private String getVersion(MavenProject project) { + for (MavenArtifact artifact : project + .findDependencies("com.google.appengine", "appengine-api-1.0-sdk")) { + String artifactVersion = artifact.getVersion(); + if (artifactVersion != null) { + return artifactVersion; + } + } + MavenPlugin plugin = project.findPlugin(myPluginGroupID, myPluginArtifactID); + return plugin != null ? plugin.getVersion() : null; + } + + @Override + protected void setupFacet(AppEngineFacet facet, MavenProject mavenProject) { + + } + + @Override + protected void reimportFacet(IdeModifiableModelsProvider modelsProvider, + Module module, + MavenRootModelAdapter rootModel, + AppEngineFacet facet, + MavenProjectsTree mavenTree, + MavenProject mavenProject, + MavenProjectChanges changes, + Map mavenProjectToModuleName, + List postTasks) { + String version = getVersion(mavenProject); + if (version != null) { + String relativePath = "/com/google/appengine/appengine-java-sdk/" + version + + "/appengine-java-sdk/appengine-java-sdk-" + version; + facet.getConfiguration().setSdkHomePath( + FileUtil.toSystemIndependentName(mavenProject.getLocalRepository().getPath()) + + relativePath); + AppEngineWebIntegration.getInstance().setupDevServer(facet.getSdk()); + final String artifactName = module.getName() + ":war exploded"; + final Artifact webArtifact = modelsProvider.getModifiableArtifactModel() + .findArtifact(artifactName); + AppEngineWebIntegration.getInstance() + .setupRunConfiguration(facet.getSdk(), webArtifact, module.getProject()); + } + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdk.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdk.java new file mode 100644 index 0000000000..997aa4c4cd --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdk.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.sdk; + +import com.intellij.execution.configurations.ParametersList; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.List; + +/** + * @author nik + */ +public interface AppEngineSdk { + + @NotNull + String getSdkHomePath(); + + @NotNull + File getAppCfgFile(); + + @NotNull + File getToolsApiJarFile(); + + @NotNull + File[] getLibraries(); + + boolean isClassInWhiteList(@NotNull String className); + + @Nullable + String getVersion(); + + boolean isMethodInBlacklist(@NotNull String className, @NotNull String methodName); + + boolean isValid(); + + @NotNull + String getOrmLibDirectoryPath(); + + @NotNull + List getUserLibraryPaths(); + + @NotNull + VirtualFile[] getOrmLibSources(); + + @NotNull + File getApplicationSchemeFile(); + + @NotNull + File getWebSchemeFile(); + + @NotNull + File[] getJspLibraries(); + + void patchJavaParametersForDevServer(@NotNull ParametersList vmParameters); +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdkManager.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdkManager.java new file mode 100644 index 0000000000..db5f923b6e --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/AppEngineSdkManager.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.sdk; + +import com.intellij.openapi.components.ServiceManager; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author nik + */ +public abstract class AppEngineSdkManager { + + public static AppEngineSdkManager getInstance() { + return ServiceManager.getService(AppEngineSdkManager.class); + } + + @NotNull + public abstract AppEngineSdk findSdk(@NotNull String sdkPath); + + @NotNull + public abstract List getValidSdks(); + +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkImpl.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkImpl.java new file mode 100644 index 0000000000..7ac14d8c0a --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkImpl.java @@ -0,0 +1,267 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.sdk.impl; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.execution.configurations.ParametersList; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.JarUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.containers.ContainerUtil; + +import gnu.trove.THashMap; +import gnu.trove.THashSet; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.appengine.model.impl.JpsAppEngineModuleExtensionImpl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; + +/** + * @author nik + */ +public class AppEngineSdkImpl implements AppEngineSdk { + + private static final Logger LOG = Logger + .getInstance("#com.intellij.appengine.sdk.impl.AppEngineSdkImpl"); + private Map> myClassesWhiteList; + private Map> myMethodsBlackList; + private final String myHomePath; + + public AppEngineSdkImpl(String homePath) { + myHomePath = homePath; + } + + @NotNull + public File getAppCfgFile() { + final String extension = SystemInfo.isWindows ? "cmd" : "sh"; + return new File(myHomePath, "bin/appcfg." + extension); + } + + @NotNull + public File getWebSchemeFile() { + return new File(myHomePath, "docs/appengine-web.xsd"); + } + + @NotNull + @Override + public File getApplicationSchemeFile() { + return new File(myHomePath, "docs/appengine-application.xsd"); + } + + @NotNull + public File getToolsApiJarFile() { + return new File(myHomePath, JpsAppEngineModuleExtensionImpl.LIB_APPENGINE_TOOLS_API_JAR); + } + + @NotNull + public File[] getLibraries() { + return getJarsFromDirectory(new File(myHomePath, "lib/shared")); + } + + @NotNull + @Override + public File[] getJspLibraries() { + return getJarsFromDirectory(new File(myHomePath, "lib/shared/jsp")); + } + + public void patchJavaParametersForDevServer(@NotNull ParametersList vmParameters) { + final String agentPath = myHomePath + "/lib/agent/appengine-agent.jar"; + if (new File(FileUtil.toSystemDependentName(agentPath)).exists()) { + vmParameters.add("-javaagent:" + agentPath); + } + String patchPath = myHomePath + "/lib/override/appengine-dev-jdk-overrides.jar"; + if (new File(FileUtil.toSystemDependentName(patchPath)).exists()) { + vmParameters.add("-Xbootclasspath/p:" + patchPath); + } + } + + private static File[] getJarsFromDirectory(File libFolder) { + List jars = new ArrayList(); + final File[] files = libFolder.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isFile() && file.getName().endsWith(".jar")) { + jars.add(file); + } + } + } + return jars.toArray(new File[jars.size()]); + } + + @NotNull + public String getSdkHomePath() { + return myHomePath; + } + + public boolean isClassInWhiteList(@NotNull String className) { + if (!isValid()) { + return true; + } + + if (myClassesWhiteList == null) { + File cachedWhiteList = getCachedWhiteListFile(); + if (cachedWhiteList.exists()) { + try { + myClassesWhiteList = AppEngineSdkUtil.loadWhiteList(cachedWhiteList); + } catch (IOException ioe) { + LOG.error(ioe); + myClassesWhiteList = Collections.emptyMap(); + } + } else { + myClassesWhiteList = AppEngineSdkUtil.computeWhiteList(getToolsApiJarFile()); + if (!myClassesWhiteList.isEmpty()) { + AppEngineSdkUtil.saveWhiteList(cachedWhiteList, myClassesWhiteList); + } + } + } + if (myClassesWhiteList.isEmpty()) { + //don't report errors if white-list wasn't properly loaded + return true; + } + + final String packageName = StringUtil.getPackageName(className); + final String name = StringUtil.getShortName(className); + final Set classes = myClassesWhiteList.get(packageName); + return classes != null && classes.contains(name); + } + + @Override + @Nullable + public String getVersion() { + return JarUtil.getJarAttribute(getToolsApiJarFile(), "com/google/appengine/tools/info/", + Attributes.Name.SPECIFICATION_VERSION); + } + + private File getCachedWhiteListFile() { + String fileName = + StringUtil.getShortName(myHomePath, '/') + Integer.toHexString(myHomePath.hashCode()) + "_" + + Long.toHexString(getToolsApiJarFile().lastModified()); + return new File(AppEngineUtilLegacy.getAppEngineSystemDir(), fileName); + } + + public boolean isMethodInBlacklist(@NotNull String className, @NotNull String methodName) { + if (myMethodsBlackList == null) { + try { + myMethodsBlackList = loadBlackList(); + } catch (IOException ioe) { + LOG.error(ioe); + myMethodsBlackList = new THashMap>(); + } + } + final Set methods = myMethodsBlackList.get(className); + return methods != null && methods.contains(methodName); + } + + public boolean isValid() { + return getToolsApiJarFile().exists() && getAppCfgFile().exists(); + } + + @NotNull + public String getOrmLibDirectoryPath() { + return getLibUserDirectoryPath() + "/orm"; + } + + @NotNull + @Override + public List getUserLibraryPaths() { + List result = new ArrayList(); + result.add(getLibUserDirectoryPath()); + File opt = new File(myHomePath, "lib/opt/user"); + ContainerUtil.addIfNotNull(result, findLatestVersion(new File(opt, "appengine-endpoints"))); + ContainerUtil.addIfNotNull(result, findLatestVersion(new File(opt, "jsr107"))); + return result; + } + + private static String findLatestVersion(File dir) { + String[] names = dir.list(); + if (names != null && names.length > 0) { + String max = Collections.max(Arrays.asList(names)); + return FileUtil.toSystemIndependentName(new File(dir, max).getAbsolutePath()); + } + return null; + } + + @NotNull + public VirtualFile[] getOrmLibSources() { + final File libsDir = new File(myHomePath, "src/orm"); + final File[] files = libsDir.listFiles(); + List roots = new ArrayList(); + if (files != null) { + for (File file : files) { + final String url = VfsUtil.getUrlForLibraryRoot(file); + final VirtualFile zipRoot = VirtualFileManager.getInstance().findFileByUrl(url); + if (zipRoot != null && zipRoot.isDirectory()) { + String fileName = file.getName(); + final String srcDirName = StringUtil.trimEnd(fileName, "-src.zip"); + final VirtualFile sourcesDir = zipRoot.findFileByRelativePath(srcDirName + "/src/java"); + if (sourcesDir != null) { + roots.add(sourcesDir); + } else { + roots.add(zipRoot); + } + } + } + } + return VfsUtilCore.toVirtualFileArray(roots); + } + + public String getLibUserDirectoryPath() { + return myHomePath + "/lib/user"; + } + + private Map> loadBlackList() throws IOException { + final InputStream stream = getClass().getResourceAsStream("/data/methodsBlacklist.txt"); + LOG.assertTrue(stream != null, "/data/methodsBlacklist.txt not found"); + final THashMap> map = new THashMap>(); + BufferedReader reader + = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); + try { + String line; + while ((line = reader.readLine()) != null) { + final int i = line.indexOf(':'); + String className = line.substring(0, i); + String methods = line.substring(i + 1); + map.put(className, new THashSet(StringUtil.split(methods, ","))); + } + } finally { + reader.close(); + } + return map; + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkManagerImpl.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkManagerImpl.java new file mode 100644 index 0000000000..bf7ef40f58 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkManagerImpl.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.sdk.impl; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdkManager; + +import com.intellij.openapi.util.text.StringUtil; + +import gnu.trove.THashMap; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +/** + * @author nik + */ +public class AppEngineSdkManagerImpl extends AppEngineSdkManager { + private final Map myPath2Sdk = new THashMap(); + + @NotNull + @Override + public AppEngineSdk findSdk(@NotNull String sdkPath) { + sdkPath = StringUtil.trimEnd(sdkPath, "/"); + if (!myPath2Sdk.containsKey(sdkPath)) { + myPath2Sdk.put(sdkPath, new AppEngineSdkImpl(sdkPath)); + } + return myPath2Sdk.get(sdkPath); + } + + @NotNull + @Override + public List getValidSdks() { + return AppEngineWebIntegration.getInstance().getSdkForConfiguredDevServers(); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkUtil.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkUtil.java new file mode 100644 index 0000000000..ef5ec2963e --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/sdk/impl/AppEngineSdkUtil.java @@ -0,0 +1,165 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.sdk.impl; + +import com.intellij.facet.ui.FacetConfigurationQuickFix; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.lang.UrlClassLoader; + +import gnu.trove.THashMap; +import gnu.trove.THashSet; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import javax.swing.JComponent; + +/** + * @author nik + */ +public class AppEngineSdkUtil { + + private static final Logger LOG + = Logger.getInstance("#com.intellij.appengine.sdk.impl.AppEngineSdkUtil"); + @NonNls + public static final String APP_ENGINE_DOWNLOAD_URL + = "http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Java"; + private static final FacetConfigurationQuickFix DOWNLOAD_SDK_QUICK_FIX + = new FacetConfigurationQuickFix("Download...") { + @Override + public void run(JComponent place) { + BrowserUtil.browse(APP_ENGINE_DOWNLOAD_URL); + } + }; + + private AppEngineSdkUtil() { + } + + public static void saveWhiteList(File cachedWhiteList, + Map> classesWhiteList) { + try { + FileUtil.createParentDirs(cachedWhiteList); + PrintWriter writer = new PrintWriter(cachedWhiteList, StandardCharsets.UTF_8.name()); + try { + for (Map.Entry> packageEntry : classesWhiteList.entrySet()) { + String packageName = packageEntry.getKey(); + writer.println("." + packageName); + final Set classes = classesWhiteList.get(packageName); + for (String someClass : classes) { + writer.println(someClass); + } + } + } finally { + writer.close(); + } + } catch (IOException ioe) { + LOG.error(ioe); + } + } + + public static Map> loadWhiteList(File input) throws IOException { + final THashMap> map = new THashMap>(); + BufferedReader reader + = new BufferedReader(new InputStreamReader( + new FileInputStream(input), StandardCharsets.UTF_8)); + try { + String line; + Set currentClasses = new THashSet(); + map.put("", currentClasses); + while ((line = reader.readLine()) != null) { + if (line.startsWith(".")) { + String packageName = line.substring(1); + currentClasses = new THashSet(); + map.put(packageName, currentClasses); + } else { + currentClasses.add(line); + } + } + } finally { + reader.close(); + } + return map; + } + + public static Map> computeWhiteList(final File toolsApiJarFile) { + try { + final THashMap> map = new THashMap>(); + final ClassLoader loader = UrlClassLoader.build().urls(toolsApiJarFile.toURI().toURL()) + .parent( + AppEngineSdkUtil.class.getClassLoader()).get(); + final Class whiteListClass = Class + .forName("com.google.apphosting.runtime.security.WhiteList", true, loader); + final Set classes = (Set) whiteListClass.getMethod("getWhiteList") + .invoke(null); + for (String qualifiedName : classes) { + final String packageName = StringUtil.getPackageName(qualifiedName); + Set classNames = map.get(packageName); + if (classNames == null) { + classNames = new THashSet(); + map.put(packageName, classNames); + } + classNames.add(StringUtil.getShortName(qualifiedName)); + } + return map; + } catch (UnsupportedClassVersionError ex) { + LOG.warn(ex); + return Collections.emptyMap(); + } catch (Exception ex) { + LOG.error(ex); + return Collections.emptyMap(); + } + } + + @NotNull + public static ValidationResult checkPath(String path) { + final AppEngineSdkImpl sdk = new AppEngineSdkImpl(path); + + final File appCfgFile = sdk.getAppCfgFile(); + if (!appCfgFile.exists()) { + return createNotFoundMessage(path, appCfgFile); + } + + final File toolsApiJarFile = sdk.getToolsApiJarFile(); + if (!toolsApiJarFile.exists()) { + return createNotFoundMessage(path, toolsApiJarFile); + } + + return ValidationResult.OK; + } + + private static ValidationResult createNotFoundMessage(@NotNull String path, @NotNull File file) { + return new ValidationResult( + "'" + path + "' is not valid App Engine SDK installation: " + "'" + file + + "' file not found", + DOWNLOAD_SDK_QUICK_FIX); + } +} diff --git a/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/util/AppEngineUtilLegacy.java b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/util/AppEngineUtilLegacy.java new file mode 100644 index 0000000000..87a0259bb6 --- /dev/null +++ b/google-cloud-tools-plugin/src/com/google/cloud/tools/intellij/appengine/util/AppEngineUtilLegacy.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.tools.intellij.appengine.util; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; + +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; +import com.intellij.packaging.impl.artifacts.ArtifactUtil; +import com.intellij.ui.ListCellRendererWrapper; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.swing.JComboBox; +import javax.swing.JList; + +/** + * @author nik + */ +public class AppEngineUtilLegacy { + + @NonNls + public static final String APP_ENGINE_WEB_XML_NAME = "appengine-web.xml"; + @NonNls + public static final String APP_ENGINE_APPLICATION_XML_NAME = "appengine-application.xml"; + @NonNls + public static final String JDO_CONFIG_XML_NAME = "jdoconfig.xml"; + @NonNls + public static final String JPA_CONFIG_XML_NAME = "persistence.xml"; + + private AppEngineUtilLegacy() { + } + + public static void setupAppEngineArtifactCombobox(@NotNull Project project, + final @NotNull JComboBox comboBox, final boolean withAppEngineFacetOnly) { + comboBox.setRenderer(new ListCellRendererWrapper() { + @Override + public void customize(JList list, Artifact value, int index, boolean selected, + boolean hasFocus) { + if (value != null) { + setIcon(value.getArtifactType().getIcon()); + setText(value.getName()); + } + } + }); + + comboBox.removeAllItems(); + for (Artifact artifact : collectAppEngineArtifacts(project, withAppEngineFacetOnly)) { + comboBox.addItem(artifact); + } + } + + public static List collectAppEngineArtifacts(@NotNull Project project, + final boolean withAppEngineFacetOnly) { + final List artifacts = new ArrayList(); + if (project.isDefault()) { + return artifacts; + } + for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) { + if (AppEngineWebIntegration.getInstance().getAppEngineTargetArtifactTypes() + .contains(artifact.getArtifactType()) + && (!withAppEngineFacetOnly || findAppEngineFacet(project, artifact) != null)) { + artifacts.add(artifact); + } + } + Collections.sort(artifacts, ArtifactManager.ARTIFACT_COMPARATOR); + return artifacts; + } + + @Nullable + public static AppEngineFacet findAppEngineFacet(@NotNull Project project, + @NotNull Artifact artifact) { + final Set modules = ArtifactUtil + .getModulesIncludedInArtifacts(Collections.singletonList(artifact), project); + for (Module module : modules) { + final AppEngineFacet appEngineFacet = AppEngineFacet.getAppEngineFacetByModule(module); + if (appEngineFacet != null) { + return appEngineFacet; + } + } + return null; + } + + public static File getAppEngineSystemDir() { + return new File(PathManager.getSystemPath(), "GoogleAppEngine"); + } + + public static List getDefaultSourceRootsToEnhance(ModuleRootModel rootModel) { + List paths = new ArrayList(); + for (String url : rootModel.getSourceRootUrls(false)) { + paths.add(VfsUtil.urlToPath(url)); + } + return paths; + } +} diff --git a/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/.idea/modules.xml b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/.idea/modules.xml new file mode 100644 index 0000000000..f4bc20a811 --- /dev/null +++ b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/appEngineGuestbook.iml b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/appEngineGuestbook.iml new file mode 100644 index 0000000000..df0677eccb --- /dev/null +++ b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/after/appEngineGuestbook.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + JPA + true + $USER_HOME$/applications/appengine-java-sdk-1.7.3 + + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/.idea/modules.xml b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/.idea/modules.xml new file mode 100644 index 0000000000..f4bc20a811 --- /dev/null +++ b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/appEngineGuestbook.iml b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/appEngineGuestbook.iml new file mode 100644 index 0000000000..251f829501 --- /dev/null +++ b/google-cloud-tools-plugin/testData/conversion/appEngineFacet/before/appEngineGuestbook.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + JPA + true + $USER_HOME$/applications/appengine-java-sdk-1.7.3 + + + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-application.xml b/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-application.xml new file mode 100644 index 0000000000..c2033ba2a7 --- /dev/null +++ b/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-application.xml @@ -0,0 +1,20 @@ + + + + + app + diff --git a/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-web.xml b/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-web.xml new file mode 100644 index 0000000000..7dba1ee52c --- /dev/null +++ b/google-cloud-tools-plugin/testData/highlighting/descriptor/appengine-web.xml @@ -0,0 +1,21 @@ + + + + + app + 1 + diff --git a/google-cloud-tools-plugin/testData/highlighting/descriptor/jdoconfig.xml b/google-cloud-tools-plugin/testData/highlighting/descriptor/jdoconfig.xml new file mode 100644 index 0000000000..bd98041843 --- /dev/null +++ b/google-cloud-tools-plugin/testData/highlighting/descriptor/jdoconfig.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.cmd b/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.cmd new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.cmd @@ -0,0 +1 @@ + diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.sh b/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.sh new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/google-cloud-tools-plugin/testData/sdk/1.3.7/bin/appcfg.sh @@ -0,0 +1 @@ + diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-application.xsd b/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-application.xsd new file mode 100644 index 0000000000..81da3eb29a --- /dev/null +++ b/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-application.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-web.xsd b/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-web.xsd new file mode 100644 index 0000000000..ae4dbf11ce --- /dev/null +++ b/google-cloud-tools-plugin/testData/sdk/1.3.7/docs/appengine-web.xsd @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/lib/appengine-tools-api.jar b/google-cloud-tools-plugin/testData/sdk/1.3.7/lib/appengine-tools-api.jar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/google-cloud-tools-plugin/testData/sdk/1.3.7/lib/user/orm/jdo.jar b/google-cloud-tools-plugin/testData/sdk/1.3.7/lib/user/orm/jdo.jar new file mode 100644 index 0000000000..7571699ff2 Binary files /dev/null and b/google-cloud-tools-plugin/testData/sdk/1.3.7/lib/user/orm/jdo.jar differ diff --git a/google-cloud-tools-plugin/testSrc/com/intellij/appengine/AppEngineCodeInsightTestCase.java b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/AppEngineCodeInsightTestCase.java new file mode 100644 index 0000000000..25d26cab9f --- /dev/null +++ b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/AppEngineCodeInsightTestCase.java @@ -0,0 +1,123 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.appengine; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; + +import com.intellij.facet.FacetManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.ModuleRootModificationUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileFilter; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.testFramework.UsefulTestCase; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import com.intellij.testFramework.fixtures.CodeInsightTestFixture; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import com.intellij.testFramework.fixtures.TestFixtureBuilder; +import com.intellij.util.CommonProcessors; + +import junit.framework.Assert; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; + +/** + * @author nik + */ +public abstract class AppEngineCodeInsightTestCase extends UsefulTestCase { + @NonNls private static final String DEFAULT_VERSION = "1.3.7"; + private JavaModuleFixtureBuilder myModuleBuilder; + private IdeaProjectTestFixture myProjectFixture; + protected CodeInsightTestFixture myCodeInsightFixture; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final TestFixtureBuilder fixtureBuilder = JavaTestFixtureFactory.createFixtureBuilder(getName()); + myModuleBuilder = fixtureBuilder.addModule(JavaModuleFixtureBuilder.class); + myProjectFixture = fixtureBuilder.getFixture(); + myCodeInsightFixture = createCodeInsightFixture(getBaseDirectoryPath()); + new WriteAction() { + @Override + protected void run(@NotNull final Result result) { + addAppEngineSupport(myProjectFixture.getModule()); + } + }.execute(); + } + + protected abstract String getBaseDirectoryPath(); + + private void addAppEngineSupport(Module module) { + final AppEngineFacet appEngine = FacetManager.getInstance(module).addFacet(AppEngineFacet.getFacetType(), "AppEngine", null); + appEngine.getConfiguration().setSdkHomePath(getSdkPath()); + + ModuleRootModificationUtil.addModuleLibrary(module, VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, getSdkPath()) + "/lib/user/orm/jdo.jar!/"); + } + + public static String getSdkPath() { + return FileUtil.toSystemIndependentName(new File(getTestDataPath(), "sdk/" + DEFAULT_VERSION).getAbsolutePath()); + } + + @Override + protected void tearDown() throws Exception { + myCodeInsightFixture.tearDown(); + super.tearDown(); + } + + protected CodeInsightTestFixture createCodeInsightFixture(final String relativeTestDataPath) throws Exception { + final String testDataPath = new File(getTestDataPath(), relativeTestDataPath).getAbsolutePath(); + final CodeInsightTestFixture codeInsightFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(myProjectFixture); + codeInsightFixture.setTestDataPath(testDataPath); + final TempDirTestFixture tempDir = codeInsightFixture.getTempDirFixture(); + myModuleBuilder.addSourceContentRoot(tempDir.getTempDirPath()); + codeInsightFixture.setUp(); + final VirtualFile dir = LocalFileSystem.getInstance().refreshAndFindFileByPath(testDataPath); + Assert.assertNotNull("Test data directory not found: " + testDataPath, dir); + VfsUtil.processFilesRecursively(dir, new CommonProcessors.CollectProcessor()); + dir.refresh(false, true); + tempDir.copyAll(testDataPath, "", new VirtualFileFilter() { + @Override + public boolean accept(VirtualFile file) { + return !file.getName().contains("_after"); + } + }); + return codeInsightFixture; + } + + public static File getTestDataPath() { + try { + URL resource = AppEngineCodeInsightTestCase.class.getResource("/sdk"); + File testDataRoot = Paths.get(resource.toURI()).toFile().getParentFile(); + return testDataRoot; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/google-cloud-tools-plugin/testSrc/com/intellij/appengine/converter/AppEngineFacetConverterTest.java b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/converter/AppEngineFacetConverterTest.java new file mode 100644 index 0000000000..c3aa091463 --- /dev/null +++ b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/converter/AppEngineFacetConverterTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.appengine.converter; + +import com.intellij.appengine.AppEngineCodeInsightTestCase; +import com.intellij.conversion.ProjectConversionTestUtil; +import com.intellij.conversion.impl.ProjectConversionUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileFilter; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PlatformTestUtil; + +import java.io.File; +import java.io.IOException; + +/** + * @author nik + */ +public class AppEngineFacetConverterTest extends PlatformTestCase { + public void testConvert() throws IOException { + File testDataRoot = new File(AppEngineCodeInsightTestCase.getTestDataPath(), "conversion/appEngineFacet"); + File testData = new File(testDataRoot, "before"); + File tempDir = FileUtil.createTempDirectory("app-engine-project", null); + FileUtil.copyDir(testData, tempDir); + ProjectConversionTestUtil.convert(tempDir.getAbsolutePath()); + File expectedDataDir = new File(testDataRoot, "after"); + PlatformTestUtil.assertDirectoriesEqual(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(expectedDataDir), + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDir), + new VirtualFileFilter() { + @Override + public boolean accept(VirtualFile file) { + return !file.getName().startsWith(ProjectConversionUtil.PROJECT_FILES_BACKUP); + } + }); + } +} diff --git a/google-cloud-tools-plugin/testSrc/com/intellij/appengine/highlighting/AppEngineDescriptorHighlightingTest.java b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/highlighting/AppEngineDescriptorHighlightingTest.java new file mode 100644 index 0000000000..a729a1399c --- /dev/null +++ b/google-cloud-tools-plugin/testSrc/com/intellij/appengine/highlighting/AppEngineDescriptorHighlightingTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.appengine.highlighting; + +import com.intellij.appengine.AppEngineCodeInsightTestCase; +import com.intellij.testFramework.TestDataPath; +import com.intellij.xml.util.CheckXmlFileWithXercesValidatorInspection; + +/** + * @author nik + */ +@TestDataPath("$CONTENT_ROOT/testData/highlighting/descriptor/") +public class AppEngineDescriptorHighlightingTest extends AppEngineCodeInsightTestCase { + public void testAppEngineWeb() throws Exception { + myCodeInsightFixture.configureByFile("appengine-web.xml"); + checkXmlHighlighting(); + } + + public void testJdoConfig() throws Exception { + myCodeInsightFixture.configureByFile("jdoconfig.xml"); + checkXmlHighlighting(); + } + + public void testApplication() throws Exception { + myCodeInsightFixture.configureByFile("appengine-application.xml"); + checkXmlHighlighting(); + } + + private void checkXmlHighlighting() { + myCodeInsightFixture.enableInspections(CheckXmlFileWithXercesValidatorInspection.class); + myCodeInsightFixture.checkHighlighting(); + } + + @Override + protected String getBaseDirectoryPath() { + return "highlighting/descriptor"; + } +} diff --git a/google-cloud-tools-plugin/ultimate/build.gradle b/google-cloud-tools-plugin/ultimate/build.gradle new file mode 100644 index 0000000000..9b403e5f88 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/build.gradle @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +sourceSets.main.resources.srcDirs = ['resources'] +sourceSets.main.java.srcDirs = ['src'] +sourceSets.test.java.srcDirs = ['testSrc'] + +intellij { + type 'IU' + + //actually only 'JavaEE' and 'GwtStudio' plugins are required, the others are transitive dependencies + plugins 'JavaEE', 'GwtStudio', 'Groovy', 'junit', 'git4idea', + 'maven', 'gradle', 'properties', 'java-i18n', + 'DatabaseTools', 'PersistenceSupport', 'JavaScriptLanguage', 'CSS', 'jsp' +} + +// Overriding paths in root build file since this module is nested one level deeper +pmd { + ruleSetFiles = files('../../custom-pmd-ruleset.xml'); +} +findbugs { + excludeFilter = file('../../findbugs-excludefilter.xml') +} + +//todo: we need to manually add dependency on 'app-engine-plugin' project because +// if we add simple project(':google-cloud-tools-plugin') it will introduce circular dependency +// for :google-cloud-tools-plugin:prepareSandbox +compileJava.dependsOn project(':google-cloud-tools-plugin').tasks.getByName("compileJava") +dependencies { + compile files(project(':google-cloud-tools-plugin').sourceSets.main.output) + compile files(project(':google-cloud-tools-plugin:jps-plugin').sourceSets.main.output) + compile files("${System.properties['java.home']}/../lib/tools.jar") + testCompile project(path: ':google-account-plugin') + testCompile project(path: ':google-cloud-tools-plugin', configuration: 'tests') + testRuntime files(project(':google-cloud-tools-plugin').sourceSets.test.resources.srcDirs) +} + +jar.archiveName = "google-cloud-tools-ultimate.jar" + + diff --git a/google-cloud-tools-plugin/ultimate/resources/META-INF/gwt-integration.xml b/google-cloud-tools-plugin/ultimate/resources/META-INF/gwt-integration.xml new file mode 100644 index 0000000000..51722cb942 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/resources/META-INF/gwt-integration.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/google-cloud-tools-plugin/ultimate/resources/META-INF/javaee-integration.xml b/google-cloud-tools-plugin/ultimate/resources/META-INF/javaee-integration.xml new file mode 100644 index 0000000000..f3f67b9210 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/resources/META-INF/javaee-integration.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineJavaeeSupportContributor.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineJavaeeSupportContributor.java new file mode 100644 index 0000000000..31fca683cb --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineJavaeeSupportContributor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.facet.impl; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineSupportProvider; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineTemplateGroupDescriptorFactory; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.javaee.DeploymentDescriptorsConstants; +import com.intellij.javaee.application.facet.JavaeeApplicationFacet; +import com.intellij.javaee.supportProvider.JavaeeFrameworkSupportContributionModel; +import com.intellij.javaee.supportProvider.JavaeeFrameworkSupportContributor; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.elements.PackagingElement; +import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.util.descriptors.ConfigFile; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public class AppEngineJavaeeSupportContributor extends JavaeeFrameworkSupportContributor { + + @Override + public void setupFrameworkSupport(JavaeeFrameworkSupportContributionModel model) { + AppEngineFacet appEngineFacet = model.getFacet(AppEngineFacet.ID); + if (appEngineFacet == null) { + return; + } + + Artifact earArtifact = model.getModifiableExplodedEarArtifact(); + JavaeeApplicationFacet applicationFacet = model.getFacet(JavaeeApplicationFacet.ID); + if (earArtifact != null && applicationFacet != null) { + VirtualFile applicationDescriptorDir = getParentDirForAppDescriptor(applicationFacet); + if (applicationDescriptorDir != null) { + VirtualFile descriptor = + AppEngineSupportProvider.createFileFromTemplate( + AppEngineTemplateGroupDescriptorFactory.APP_ENGINE_APPLICATION_XML_TEMPLATE, + applicationDescriptorDir, AppEngineUtilLegacy.APP_ENGINE_APPLICATION_XML_NAME); + if (descriptor != null) { + PackagingElement packagingElement = PackagingElementFactory.getInstance() + .createFileCopy(descriptor.getPath(), null); + PackagingElementFactory.getInstance() + .getOrCreateDirectory(earArtifact.getRootElement(), "META-INF") + .addFirstChild(packagingElement); + } + } + } + Artifact artifactToDeploy = model.getExplodedEarArtifact(); + if (artifactToDeploy == null) { + artifactToDeploy = model.getExplodedWarArtifact(); + } + if (artifactToDeploy != null) { + AppEngineSdk sdk = appEngineFacet.getSdk(); + AppEngineWebIntegration.getInstance() + .setupRunConfiguration(sdk, artifactToDeploy, model.getProject()); + } + } + + private static VirtualFile getParentDirForAppDescriptor( + @NotNull JavaeeApplicationFacet applicationFacet) { + ConfigFile configFile = applicationFacet.getDescriptorsContainer() + .getConfigFile(DeploymentDescriptorsConstants.APPLICATION_XML_META_DATA); + if (configFile != null) { + VirtualFile file = configFile.getVirtualFile(); + if (file != null) { + return file.getParent(); + } + } + return null; + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineUltimateWebIntegration.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineUltimateWebIntegration.java new file mode 100644 index 0000000000..0fe09808c3 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/facet/impl/AppEngineUltimateWebIntegration.java @@ -0,0 +1,198 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.facet.impl; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineSupportProvider; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineWebIntegration; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.appengine.server.instance.AppEngineServerModel; +import com.intellij.appengine.server.integration.AppEngineServerData; +import com.intellij.appengine.server.integration.AppEngineServerIntegration; +import com.intellij.appengine.server.run.AppEngineServerConfigurationType; +import com.intellij.execution.RunManager; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.facet.FacetManager; +import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider.FrameworkDependency; +import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel; +import com.intellij.javaee.JavaeePersistenceDescriptorsConstants; +import com.intellij.javaee.appServerIntegrations.ApplicationServer; +import com.intellij.javaee.artifact.JavaeeArtifactUtil; +import com.intellij.javaee.facet.JavaeeFrameworkSupportInfoCollector; +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.javaee.run.configuration.J2EEConfigurationFactory; +import com.intellij.javaee.serverInstances.ApplicationServersManager; +import com.intellij.javaee.web.artifact.WebArtifactUtil; +import com.intellij.javaee.web.facet.WebFacet; +import com.intellij.jpa.facet.JpaFacet; +import com.intellij.jpa.facet.JpaFacetType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.DependencyScope; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.packaging.impl.run.BuildArtifactsBeforeRunTaskProvider; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.descriptors.ConfigFile; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineUltimateWebIntegration extends AppEngineWebIntegration { + + @NotNull + @Override + public ArtifactType getAppEngineWebArtifactType() { + return WebArtifactUtil.getInstance().getExplodedWarArtifactType(); + } + + @Nullable + @Override + public ArtifactType getAppEngineApplicationArtifactType() { + return JavaeeArtifactUtil.getInstance().getExplodedEarArtifactType(); + } + + public VirtualFile suggestParentDirectoryForAppEngineWebXml(@NotNull Module module, + @NotNull ModifiableRootModel rootModel) { + final WebFacet webFacet = ContainerUtil.getFirstItem(WebFacet.getInstances(module)); + if (webFacet == null) { + return null; + } + + ConfigFile configFile = webFacet.getWebXmlDescriptor(); + if (configFile == null) { + return null; + } + + final VirtualFile webXml = configFile.getVirtualFile(); + if (webXml == null) { + return null; + } + + return webXml.getParent(); + } + + public void setupJpaSupport(@NotNull Module module, @NotNull VirtualFile persistenceXml) { + JpaFacet facet = FacetManager.getInstance(module).getFacetByType(JpaFacet.ID); + if (facet == null) { + final JpaFacet jpaFacet = FacetManager.getInstance(module).addFacet( + JpaFacetType.getInstance(), JpaFacetType.getInstance().getDefaultFacetName(), null); + jpaFacet.getDescriptorsContainer().getConfiguration().replaceConfigFile( + JavaeePersistenceDescriptorsConstants.PERSISTENCE_XML_META_DATA, persistenceXml.getUrl()); + } + } + + public void setupRunConfiguration(@NotNull AppEngineSdk sdk, + Artifact artifact, + @NotNull Project project) { + final ApplicationServer appServer = getOrCreateAppServer(sdk); + if (appServer != null) { + AppEngineServerConfigurationType configurationType = AppEngineServerConfigurationType + .getInstance(); + List list = RunManager.getInstance(project) + .getConfigurationSettingsList(configurationType); + if (list.isEmpty()) { + final RunnerAndConfigurationSettings settings = J2EEConfigurationFactory.getInstance() + .addAppServerConfiguration(project, configurationType.getLocalFactory(), appServer); + if (artifact != null) { + final CommonModel configuration = (CommonModel) settings.getConfiguration(); + ((AppEngineServerModel) configuration.getServerModel()).setArtifact(artifact); + BuildArtifactsBeforeRunTaskProvider + .setBuildArtifactBeforeRun(project, configuration, artifact); + } + } + } + } + + @Override + public void addDevServerToModuleDependencies(@NotNull ModifiableRootModel rootModel, + @NotNull AppEngineSdk sdk) { + final ApplicationServer appServer = getOrCreateAppServer(sdk); + if (appServer != null) { + rootModel.addLibraryEntry(appServer.getLibrary()).setScope(DependencyScope.PROVIDED); + } + } + + @Override + public void addLibraryToArtifact(@NotNull Library library, @NotNull Artifact artifact, + @NotNull Project project) { + WebArtifactUtil.getInstance().addLibrary(library, artifact, project); + } + + public void setupDevServer(@NotNull final AppEngineSdk sdk) { + getOrCreateAppServer(sdk); + } + + private static ApplicationServer getOrCreateAppServer(AppEngineSdk sdk) { + if (!sdk.isValid()) { + return null; + } + final ApplicationServersManager serversManager = ApplicationServersManager.getInstance(); + final AppEngineServerIntegration integration = AppEngineServerIntegration.getInstance(); + + final List servers = serversManager.getApplicationServers(integration); + File sdkHomeFile = new File(sdk.getSdkHomePath()); + for (ApplicationServer server : servers) { + final String path = ((AppEngineServerData) server.getPersistentData()).getSdkPath(); + if (FileUtil.filesEqual(sdkHomeFile, new File(path))) { + return server; + } + } + + return ApplicationServersManager.getInstance() + .createServer(integration, new AppEngineServerData(sdk.getSdkHomePath())); + } + + public List getSdkForConfiguredDevServers() { + final List servers = ApplicationServersManager.getInstance() + .getApplicationServers(AppEngineServerIntegration.getInstance()); + List sdkList = new ArrayList(); + for (ApplicationServer server : servers) { + final AppEngineSdk sdk = ((AppEngineServerData) server.getPersistentData()).getSdk(); + if (sdk.isValid()) { + sdkList.add(sdk); + } + } + return sdkList; + } + + @Override + public void registerFrameworkInModel(FrameworkSupportModel model, AppEngineFacet appEngineFacet) { + JavaeeFrameworkSupportInfoCollector.getOrCreateCollector(model) + .setFacet(AppEngineFacet.ID, appEngineFacet); + } + + @Override + @NotNull + public List getAppEngineFrameworkDependencies() { + return Arrays.asList(FrameworkDependency.required("web"), FrameworkDependency.optional( + AppEngineSupportProvider.JPA_FRAMEWORK_ID)); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServer.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServer.java new file mode 100644 index 0000000000..af3e7a347d --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.gwt; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.appengine.server.integration.AppEngineServerData; +import com.intellij.execution.configurations.JavaParameters; +import com.intellij.execution.configurations.ParametersList; +import com.intellij.gwt.facet.GwtFacet; +import com.intellij.gwt.run.GwtDevModeServer; +import com.intellij.javaee.appServerIntegrations.ApplicationServer; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ArrayUtil; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +import javax.swing.Icon; + +/** + * @author nik + */ +public class AppEngineGwtServer extends GwtDevModeServer { + + private final ApplicationServer myServer; + + public AppEngineGwtServer(@NotNull ApplicationServer server) { + super("app-engine:" + server.getName(), server.getName()); + myServer = server; + } + + @Override + public Icon getIcon() { + return GoogleCloudToolsIcons.APP_ENGINE; + } + + @Override + public void patchParameters(@NotNull JavaParameters parameters, String originalOutputDir, + @NotNull GwtFacet gwtFacet) { + final ParametersList programParameters = parameters.getProgramParametersList(); + programParameters.add("-server"); + programParameters.add("com.google.appengine.tools.development.gwt.AppEngineLauncher"); + + final AppEngineSdk sdk = ((AppEngineServerData) myServer.getPersistentData()).getSdk(); + sdk.patchJavaParametersForDevServer(parameters.getVMParametersList()); + + //actually these jars are added by AppEngine dev server automatically. But they need to be + // added to classpath before gwt-dev.jar, because otherwise wrong jsp compiler version will be + // used (see IDEA-63068) + for (File jar : ArrayUtil.mergeArrays(sdk.getLibraries(), sdk.getJspLibraries())) { + parameters.getClassPath().addFirst(FileUtil.toSystemIndependentName(jar.getAbsolutePath())); + } + + parameters.getClassPath().add(sdk.getToolsApiJarFile()); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServerProvider.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServerProvider.java new file mode 100644 index 0000000000..85bbb57b3e --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/AppEngineGwtServerProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.gwt; + +import com.intellij.appengine.server.integration.AppEngineServerIntegration; +import com.intellij.gwt.run.GwtDevModeServer; +import com.intellij.gwt.run.GwtDevModeServerProvider; +import com.intellij.javaee.appServerIntegrations.ApplicationServer; +import com.intellij.javaee.serverInstances.ApplicationServersManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineGwtServerProvider extends GwtDevModeServerProvider { + + @Override + public List getServers() { + final List servers = ApplicationServersManager.getInstance() + .getApplicationServers(AppEngineServerIntegration.getInstance()); + final List result = new ArrayList(); + for (ApplicationServer server : servers) { + result.add(new AppEngineGwtServer(server)); + } + return result; + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/GwtNativeMethodsHandler.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/GwtNativeMethodsHandler.java new file mode 100644 index 0000000000..2360e01244 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/gwt/GwtNativeMethodsHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.gwt; + +import com.google.cloud.tools.intellij.appengine.inspections.AppEngineForbiddenCodeHandler; + +import com.intellij.gwt.jsinject.JsInjector; +import com.intellij.psi.PsiMethod; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public class GwtNativeMethodsHandler extends AppEngineForbiddenCodeHandler { + @Override + public boolean isNativeMethodAllowed(@NotNull PsiMethod method) { + return JsInjector.isJsniMethod(method); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.form b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.form new file mode 100644 index 0000000000..d297887c39 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.form @@ -0,0 +1,64 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.java new file mode 100644 index 0000000000..9941c1812e --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineRunConfigurationEditor.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.instance; + +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.impl.run.BuildArtifactsBeforeRunTaskProvider; +import com.intellij.ui.PanelWithAnchor; +import com.intellij.ui.RawCommandLineEditor; +import com.intellij.ui.components.JBLabel; + +import org.jetbrains.annotations.NotNull; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * @author nik + */ +public class AppEngineRunConfigurationEditor extends SettingsEditor implements + PanelWithAnchor { + + private JPanel myMainPanel; + private JComboBox myArtifactComboBox; + private JTextField myPortField; + private RawCommandLineEditor myServerParametersEditor; + private JBLabel myWebArtifactToDeployLabel; + private JBLabel myPortLabel; + private JBLabel myServerParametersLabel; + private final Project myProject; + private Artifact myLastSelectedArtifact; + private JComponent anchor; + + public AppEngineRunConfigurationEditor(Project project) { + myProject = project; + myArtifactComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + onArtifactChanged(); + } + }); + + setAnchor(myWebArtifactToDeployLabel); + } + + private void onArtifactChanged() { + final Artifact selectedArtifact = getSelectedArtifact(); + if (!Comparing.equal(myLastSelectedArtifact, selectedArtifact)) { + if (myLastSelectedArtifact != null) { + BuildArtifactsBeforeRunTaskProvider + .setBuildArtifactBeforeRunOption(myMainPanel, myProject, myLastSelectedArtifact, false); + } + if (selectedArtifact != null) { + BuildArtifactsBeforeRunTaskProvider + .setBuildArtifactBeforeRunOption(myMainPanel, myProject, selectedArtifact, true); + } + myLastSelectedArtifact = selectedArtifact; + } + } + + protected void resetEditorFrom(CommonModel commonModel) { + final AppEngineServerModel serverModel = (AppEngineServerModel) commonModel.getServerModel(); + myPortField.setText(String.valueOf(serverModel.getLocalPort())); + final Artifact artifact = serverModel.getArtifact(); + myArtifactComboBox.setSelectedItem(artifact); + if (artifact == null && myArtifactComboBox.getItemCount() == 1) { + myArtifactComboBox.setSelectedIndex(0); + } + myServerParametersEditor.setDialogCaption("Server Parameters"); + myServerParametersEditor.setText(serverModel.getServerParameters()); + } + + protected void applyEditorTo(CommonModel commonModel) throws ConfigurationException { + final AppEngineServerModel serverModel = (AppEngineServerModel) commonModel.getServerModel(); + try { + serverModel.setPort(Integer.parseInt(myPortField.getText())); + } catch (NumberFormatException nfe) { + throw new ConfigurationException("'" + myPortField.getText() + "' is not valid port number"); + } + serverModel.setServerParameters(myServerParametersEditor.getText()); + serverModel.setArtifact(getSelectedArtifact()); + } + + private Artifact getSelectedArtifact() { + return (Artifact) myArtifactComboBox.getSelectedItem(); + } + + @NotNull + protected JComponent createEditor() { + AppEngineUtilLegacy.setupAppEngineArtifactCombobox(myProject, myArtifactComboBox, false); + return myMainPanel; + } + + @Override + public JComponent getAnchor() { + return anchor; + } + + @Override + public void setAnchor(JComponent anchor) { + this.anchor = anchor; + myWebArtifactToDeployLabel.setAnchor(anchor); + myPortLabel.setAnchor(anchor); + myServerParametersLabel.setAnchor(anchor); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerInstance.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerInstance.java new file mode 100644 index 0000000000..e93e4e9d2d --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerInstance.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.instance; + +import com.intellij.debugger.DebuggerManager; +import com.intellij.debugger.engine.DebugProcess; +import com.intellij.debugger.engine.DebugProcessAdapter; +import com.intellij.debugger.engine.DefaultJSPPositionManager; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.javaee.facet.JavaeeFacetUtil; +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.javaee.serverInstances.DefaultServerInstance; +import com.intellij.openapi.project.Project; + +/** + * @author nik + */ +public class AppEngineServerInstance extends DefaultServerInstance { + + public AppEngineServerInstance(CommonModel runConfiguration) { + super(runConfiguration); + } + + @Override + public void start(ProcessHandler processHandler) { + super.start(processHandler); + final Project project = getCommonModel().getProject(); + DebuggerManager.getInstance(project) + .addDebugProcessListener(processHandler, new DebugProcessAdapter() { + @Override + public void processAttached(DebugProcess process) { + process.appendPositionManager(new DefaultJSPPositionManager(process, + JavaeeFacetUtil.getInstance().getAllJavaeeFacets(project)) { + @Override + protected String getGeneratedClassesPackage() { + return "org.apache.jsp"; + } + }); + } + }); + } + + @Override + public void shutdown() { + super.shutdown(); + ProcessHandler processHandler = getProcessHandler(); + if (processHandler instanceof OSProcessHandler) { + //todo[nik] remove later. This fix is necessary only for IDEA 8.x + ((OSProcessHandler) processHandler).getProcess().destroy(); + } + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerModel.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerModel.java new file mode 100644 index 0000000000..2be537c1ee --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/instance/AppEngineServerModel.java @@ -0,0 +1,230 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.instance; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.util.AppEngineUtilLegacy; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.RuntimeConfigurationError; +import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.execution.configurations.RuntimeConfigurationWarning; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.javaee.deployment.DeploymentProvider; +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.javaee.run.configuration.DeploysArtifactsOnStartupOnly; +import com.intellij.javaee.run.configuration.ServerModel; +import com.intellij.javaee.run.execution.DefaultOutputProcessor; +import com.intellij.javaee.run.execution.OutputProcessor; +import com.intellij.javaee.serverInstances.J2EEServerInstance; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactPointer; +import com.intellij.packaging.artifacts.ArtifactPointerManager; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; +import com.intellij.util.xmlb.XmlSerializer; +import com.intellij.util.xmlb.annotations.Tag; + +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class AppEngineServerModel implements ServerModel, DeploysArtifactsOnStartupOnly { + + private ArtifactPointer myArtifactPointer; + private int myPort = 8080; + private String myServerParameters = ""; + private CommonModel myCommonModel; + + @Override + public J2EEServerInstance createServerInstance() throws ExecutionException { + return new AppEngineServerInstance(myCommonModel); + } + + @Override + public DeploymentProvider getDeploymentProvider() { + return null; + } + + @Override + @NotNull + public String getDefaultUrlForBrowser() { + return "http://" + myCommonModel.getHost() + ":" + myPort; + } + + @Override + public SettingsEditor getEditor() { + return new AppEngineRunConfigurationEditor(myCommonModel.getProject()); + } + + @Override + public OutputProcessor createOutputProcessor(ProcessHandler processHandler, + J2EEServerInstance serverInstance) { + return new DefaultOutputProcessor(processHandler); + } + + @Override + public List> getAddressesToCheck() { + return Collections.singletonList(Pair.create(myCommonModel.getHost(), myPort)); + } + + @Override + public boolean isResourcesReloadingSupported() { + return myCommonModel.isLocal(); + } + + @Override + public List getArtifactsToDeploy() { + return ContainerUtil.createMaybeSingletonList(getArtifact()); + } + + @Override + public void checkConfiguration() throws RuntimeConfigurationException { + Artifact artifact; + if (myArtifactPointer == null || (artifact = myArtifactPointer.getArtifact()) == null) { + throw new RuntimeConfigurationError("Artifact isn't specified"); + } + + final AppEngineFacet facet = AppEngineUtilLegacy + .findAppEngineFacet(myCommonModel.getProject(), artifact); + if (facet == null) { + throw new RuntimeConfigurationWarning( + "App Engine facet not found in '" + artifact.getName() + "' artifact"); + } + } + + @Override + public int getDefaultPort() { + return 8080; + } + + @Override + public void setCommonModel(CommonModel commonModel) { + myCommonModel = commonModel; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public int getLocalPort() { + return myPort; + } + + @Override + public void readExternal(Element element) throws InvalidDataException { + final AppEngineModelSettings settings = new AppEngineModelSettings(); + XmlSerializer.deserializeInto(settings, element); + myPort = settings.getPort(); + myServerParameters = settings.getServerParameters(); + final String artifactName = settings.getArtifact(); + if (artifactName != null) { + myArtifactPointer = ArtifactPointerManager.getInstance(myCommonModel.getProject()) + .createPointer(artifactName); + } else { + myArtifactPointer = null; + } + } + + @Override + public void writeExternal(Element element) throws WriteExternalException { + XmlSerializer + .serializeInto(new AppEngineModelSettings(myPort, myArtifactPointer, myServerParameters), + element, new SkipDefaultValuesSerializationFilters()); + } + + @Nullable + public Artifact getArtifact() { + return myArtifactPointer != null ? myArtifactPointer.getArtifact() : null; + } + + public void setPort(int port) { + myPort = port; + } + + public String getServerParameters() { + return myServerParameters; + } + + public void setServerParameters(String serverParameters) { + myServerParameters = serverParameters; + } + + public void setArtifact(@Nullable Artifact artifact) { + if (artifact != null) { + myArtifactPointer = ArtifactPointerManager.getInstance(myCommonModel.getProject()) + .createPointer(artifact); + } else { + myArtifactPointer = null; + } + } + + public static class AppEngineModelSettings { + + @Tag("port") + private int myPort = 8080; + @Tag("artifact") + private String myArtifact; + @Tag("server-parameters") + private String myServerParameters = ""; + + public AppEngineModelSettings() { + } + + public AppEngineModelSettings(int port, ArtifactPointer pointer, String serverParameters) { + myPort = port; + myServerParameters = serverParameters; + myArtifact = pointer != null ? pointer.getArtifactName() : null; + } + + public int getPort() { + return myPort; + } + + public void setPort(int port) { + myPort = port; + } + + public String getArtifact() { + return myArtifact; + } + + public void setArtifact(String artifact) { + myArtifact = artifact; + } + + public String getServerParameters() { + return myServerParameters; + } + + public void setServerParameters(String serverParameters) { + myServerParameters = serverParameters; + } + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerData.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerData.java new file mode 100644 index 0000000000..edf9277ad5 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerData.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.integration; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdkManager; + +import com.intellij.javaee.appServerIntegrations.ApplicationServerPersistentData; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; + +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public class AppEngineServerData implements ApplicationServerPersistentData { + private String mySdkPath; + + public AppEngineServerData(@NotNull String sdkPath) { + mySdkPath = sdkPath; + } + + @NotNull + public String getSdkPath() { + return mySdkPath; + } + + @NotNull + public AppEngineSdk getSdk() { + return AppEngineSdkManager.getInstance().findSdk(mySdkPath); + } + + public void setSdkPath(@NotNull String sdkPath) { + mySdkPath = sdkPath; + } + + public void readExternal(Element element) throws InvalidDataException { + mySdkPath = element.getChildTextTrim("sdk-path"); + } + + public void writeExternal(Element element) throws WriteExternalException { + element.addContent(new Element("sdk-path").addContent(mySdkPath)); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.form b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.form new file mode 100644 index 0000000000..ced981ee06 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.form @@ -0,0 +1,44 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.java new file mode 100644 index 0000000000..6067d03f08 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerEditor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.integration; + +import com.intellij.javaee.appServerIntegrations.ApplicationServerPersistentDataEditor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.io.FileUtil; + +import org.jetbrains.annotations.NotNull; + +import javax.swing.JComponent; +import javax.swing.JPanel; + +/** + * @author nik + */ +public class AppEngineServerEditor extends + ApplicationServerPersistentDataEditor { + + private JPanel myMainPanel; + private TextFieldWithBrowseButton mySdkHomeField; + + public AppEngineServerEditor() { + mySdkHomeField + .addBrowseFolderListener("Google App Engine SDK", "Specify Google App Engine Java SDK home", + null, FileChooserDescriptorFactory.createSingleFolderDescriptor()); + } + + protected void resetEditorFrom(AppEngineServerData data) { + mySdkHomeField.setText(FileUtil.toSystemDependentName(data.getSdkPath())); + } + + protected void applyEditorTo(AppEngineServerData data) { + data.setSdkPath(FileUtil.toSystemIndependentName(mySdkHomeField.getText())); + } + + @NotNull + protected JComponent createEditor() { + return myMainPanel; + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerHelper.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerHelper.java new file mode 100644 index 0000000000..d44e179709 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.integration; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.javaee.appServerIntegrations.ApplicationServerHelper; +import com.intellij.javaee.appServerIntegrations.ApplicationServerInfo; +import com.intellij.javaee.appServerIntegrations.ApplicationServerPersistentData; +import com.intellij.javaee.appServerIntegrations.ApplicationServerPersistentDataEditor; +import com.intellij.javaee.appServerIntegrations.CantFindApplicationServerJarsException; + +/** + * @author nik + */ +public class AppEngineServerHelper implements ApplicationServerHelper { + + public ApplicationServerInfo getApplicationServerInfo( + ApplicationServerPersistentData persistentData) + throws CantFindApplicationServerJarsException { + final AppEngineSdk sdk = ((AppEngineServerData) persistentData).getSdk(); + String version = sdk.getVersion(); + return new ApplicationServerInfo(sdk.getLibraries(), + "AppEngine Dev" + (version != null ? " " + version : "")); + } + + public ApplicationServerPersistentData createPersistentDataEmptyInstance() { + return new AppEngineServerData(""); + } + + public ApplicationServerPersistentDataEditor createConfigurable() { + return new AppEngineServerEditor(); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerIntegration.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerIntegration.java new file mode 100644 index 0000000000..5a66108d2d --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/integration/AppEngineServerIntegration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.integration; + +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.javaee.appServerIntegrations.AppServerIntegration; +import com.intellij.javaee.appServerIntegrations.ApplicationServerHelper; +import com.intellij.javaee.appServerIntegrations.ApplicationServerPersistentDataEditor; +import com.intellij.javaee.openapi.ex.AppServerIntegrationsManager; + +import javax.swing.Icon; + +/** + * @author nik + */ +public class AppEngineServerIntegration extends AppServerIntegration { + + private final AppEngineServerHelper myServerHelper; + + public static AppEngineServerIntegration getInstance() { + return AppServerIntegrationsManager.getInstance() + .getIntegration(AppEngineServerIntegration.class); + } + + public AppEngineServerIntegration() { + myServerHelper = new AppEngineServerHelper(); + } + + public Icon getIcon() { + return GoogleCloudToolsIcons.APP_ENGINE; + } + + public String getPresentableName() { + return "Google App Engine Dev Server"; + } + + @Override + public ApplicationServerPersistentDataEditor createNewServerEditor() { + //Google App Engine server should not be shown in 'Application Server' combobox in the new + // project wizard because there is a special 'Google App Engine' option + return null; + } + + @Override + public ApplicationServerHelper getApplicationServerHelper() { + return myServerHelper; + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationProducer.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationProducer.java new file mode 100644 index 0000000000..ae0732fe1c --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationProducer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * User: anna + * Date: 13-May-2010 + */ + +package com.intellij.appengine.server.run; + +import com.intellij.javaee.run.configuration.J2EEConfigurationProducer; + +public class AppEngineServerConfigurationProducer extends J2EEConfigurationProducer { + + public AppEngineServerConfigurationProducer() { + super(AppEngineServerConfigurationType.getInstance()); + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationType.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationType.java new file mode 100644 index 0000000000..e799d39a3b --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerConfigurationType.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.run; + +import com.google.cloud.tools.intellij.ui.GoogleCloudToolsIcons; + +import com.intellij.appengine.server.instance.AppEngineServerModel; +import com.intellij.appengine.server.integration.AppEngineServerIntegration; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.ConfigurationTypeUtil; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.javaee.appServerIntegrations.AppServerIntegration; +import com.intellij.javaee.run.configuration.J2EEConfigurationFactory; +import com.intellij.javaee.run.configuration.J2EEConfigurationType; +import com.intellij.openapi.project.Project; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Icon; + +/** + * @author nik + */ +public class AppEngineServerConfigurationType extends J2EEConfigurationType { + + public static AppEngineServerConfigurationType getInstance() { + return ConfigurationTypeUtil.findConfigurationType(AppEngineServerConfigurationType.class); + } + + @SuppressWarnings("checkstyle:abbreviationAsWordInName") + protected RunConfiguration createJ2EEConfigurationTemplate(ConfigurationFactory factory, + Project project, boolean isLocal) { + final AppEngineServerModel serverModel = new AppEngineServerModel(); + return J2EEConfigurationFactory.getInstance() + .createJ2EERunConfiguration(factory, project, serverModel, + getIntegration(), isLocal, new AppEngineServerStartupPolicy()); + } + + public String getDisplayName() { + return "Google AppEngine Dev Server"; + } + + public String getConfigurationTypeDescription() { + return "Google AppEngine Dev Server run configuration"; + } + + @Nullable + public Icon getIcon() { + return GoogleCloudToolsIcons.APP_ENGINE; + } + + @Override + public ConfigurationFactory[] getConfigurationFactories() { + return new ConfigurationFactory[]{super.getConfigurationFactories()[0]}; + } + + public AppServerIntegration getIntegration() { + return AppEngineServerIntegration.getInstance(); + } + + @NotNull + public String getId() { + return "GoogleAppEngineDevServer"; + } +} diff --git a/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerStartupPolicy.java b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerStartupPolicy.java new file mode 100644 index 0000000000..92b1770ad5 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/src/com/intellij/appengine/server/run/AppEngineServerStartupPolicy.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.server.run; + +import com.google.cloud.tools.intellij.appengine.sdk.AppEngineSdk; + +import com.intellij.appengine.server.instance.AppEngineServerModel; +import com.intellij.appengine.server.integration.AppEngineServerData; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.JavaParameters; +import com.intellij.execution.configurations.ParametersList; +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.javaee.run.configuration.JavaCommandLineStartupPolicy; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.packaging.artifacts.Artifact; + +import java.io.File; + +/** + * @author nik + */ +public class AppEngineServerStartupPolicy implements JavaCommandLineStartupPolicy { + + public JavaParameters createCommandLine(CommonModel commonModel) throws ExecutionException { + final AppEngineServerData data = (AppEngineServerData) commonModel.getApplicationServer() + .getPersistentData(); + final AppEngineSdk sdk = data.getSdk(); + if (StringUtil.isEmpty(sdk.getSdkHomePath())) { + throw new ExecutionException("Path to App Engine SDK isn't specified"); + } + final File toolsApiJarFile = sdk.getToolsApiJarFile(); + if (!toolsApiJarFile.exists()) { + throw new ExecutionException( + "'" + sdk.getSdkHomePath() + "' isn't valid App Engine SDK installation: '" + + toolsApiJarFile.getAbsolutePath() + "' not found"); + } + final JavaParameters javaParameters = new JavaParameters(); + javaParameters.getClassPath().add(toolsApiJarFile.getAbsolutePath()); + javaParameters.setMainClass("com.google.appengine.tools.development.DevAppServerMain"); + + final AppEngineServerModel serverModel = (AppEngineServerModel) commonModel.getServerModel(); + final Artifact artifact = serverModel.getArtifact(); + if (artifact == null) { + throw new ExecutionException("Artifact isn't specified"); + } + + final ParametersList parameters = javaParameters.getProgramParametersList(); + parameters.addParametersString(serverModel.getServerParameters()); + parameters.replaceOrAppend("-p", ""); + parameters.replaceOrAppend("--port", ""); + parameters.add("-p", String.valueOf(serverModel.getLocalPort())); + parameters.add("--disable_update_check"); + + final String outputPath = artifact.getOutputPath(); + if (outputPath == null || outputPath.length() == 0) { + throw new ExecutionException( + "Output path isn't specified for '" + artifact.getName() + "' artifact"); + } + final String explodedPathParameter = FileUtil.toSystemDependentName(outputPath); + parameters.add(explodedPathParameter); + javaParameters.setWorkingDirectory(explodedPathParameter); + final ParametersList vmParameters = javaParameters.getVMParametersList(); + sdk.patchJavaParametersForDevServer(vmParameters); + if (SystemInfo.isMac) { + vmParameters.add("-XstartOnFirstThread"); + } + return javaParameters; + } +} diff --git a/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/appengine/supportProvider/AppEngineSupportProviderTest.java b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/appengine/supportProvider/AppEngineSupportProviderTest.java new file mode 100644 index 0000000000..a35360adae --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/appengine/supportProvider/AppEngineSupportProviderTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.appengine.supportProvider; + +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFacet; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineFrameworkType; +import com.google.cloud.tools.intellij.appengine.facet.AppEngineSupportProvider; + +import com.intellij.appengine.AppEngineCodeInsightTestCase; +import com.intellij.appengine.server.run.AppEngineServerConfigurationType; +import com.intellij.compiler.artifacts.ArtifactsTestUtil; +import com.intellij.execution.RunManager; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.facet.FacetManager; +import com.intellij.framework.addSupport.FrameworkSupportInModuleConfigurable; +import com.intellij.javaee.JavaeeVersion; +import com.intellij.javaee.application.facet.JavaeeApplicationFacet; +import com.intellij.javaee.application.framework.JavaeeApplicationFrameworkType; +import com.intellij.javaee.application.framework.JavaeeApplicationFrameworkVersion; +import com.intellij.javaee.model.enums.WebAppVersion; +import com.intellij.javaee.run.configuration.CommonModel; +import com.intellij.javaee.supportProvider.JavaeeFrameworkSupportProviderTestCase; +import com.intellij.javaee.web.facet.WebFacet; +import com.intellij.javaee.web.framework.WebFrameworkType; +import com.intellij.javaee.web.framework.WebFrameworkVersion; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author nik + */ +public class AppEngineSupportProviderTest extends JavaeeFrameworkSupportProviderTestCase { + public void testAppEngine() { + setupAppEngine(); + addSupport(); + + final AppEngineFacet appEngineFacet = getFacet(AppEngineFacet.ID); + assertNull(FacetManager.getInstance(myModule).getFacetByType(WebFacet.ID)); + assertEmpty(appEngineFacet.getConfiguration().getFilesToEnhance()); + final String moduleName = myModule.getName(); + ArtifactsTestUtil.assertLayout(myProject, moduleName, "\n" + + " WEB-INF/\n" + + " classes/\n" + + " module:" + moduleName + "\n" + + " lib/\n" + + " lib:AppEngine API(project)\n"); + } + + private void setupAppEngine() { + FrameworkSupportInModuleConfigurable configurable = selectFramework(AppEngineFrameworkType.ID); + AppEngineSupportProvider.setSdkPath(configurable, AppEngineCodeInsightTestCase.getSdkPath()); + } + + private void assertRunConfigurationCreated(Artifact artifactToDeploy) { + List list = RunManager.getInstance(myProject).getConfigurationsList(AppEngineServerConfigurationType.getInstance()); + CommonModel configuration = assertInstanceOf(assertOneElement(list), CommonModel.class); + assertSameElements(configuration.getDeployedArtifacts(), artifactToDeploy); + } + + public void testAppEngineWithWeb() { + setupAppEngine(); + selectFramework(WebFacet.ID); + selectVersion(WebFrameworkType.getInstance(), new WebFrameworkVersion(WebAppVersion.WebAppVersion_2_5)); + addSupport(); + + getFacet(AppEngineFacet.ID); + assertFileExist("web/WEB-INF/web.xml"); + assertFileExist("web/WEB-INF/appengine-web.xml"); + + final String moduleName = myModule.getName(); + Artifact artifact = ArtifactsTestUtil.findArtifact(myProject, moduleName + ":war exploded"); + ArtifactsTestUtil.assertLayout(artifact.getRootElement(), "\n" + + " javaee-resources:Web(" + moduleName + ")\n" + + " WEB-INF/\n" + + " classes/\n" + + " module:" + moduleName + "\n" + + " lib/\n" + + " lib:AppEngine API(project)\n"); + assertRunConfigurationCreated(artifact); + } + + public void testAppEngineWithEar() { + setupAppEngine(); + selectFramework(WebFacet.ID); + selectFramework(JavaeeApplicationFacet.ID); + selectVersion(WebFrameworkType.getInstance(), new WebFrameworkVersion(WebAppVersion.WebAppVersion_2_5)); + selectVersion(JavaeeApplicationFrameworkType.getInstance(), new JavaeeApplicationFrameworkVersion(JavaeeVersion.JAVAEE_6)); + addSupport(); + + getFacet(AppEngineFacet.ID); + assertFileExist("web/WEB-INF/web.xml"); + assertFileExist("web/WEB-INF/appengine-web.xml"); + assertFileExist("META-INF/application.xml"); + VirtualFile descriptor = assertFileExist("META-INF/appengine-application.xml"); + + final String moduleName = myModule.getName(); + Artifact artifact = ArtifactsTestUtil.findArtifact(myProject, moduleName + ":ear exploded"); + ArtifactsTestUtil.assertLayout(artifact.getRootElement(), "\n" + + " javaee-resources:javaEEApplication(" + moduleName + ")\n" + + " web.war/\n" + + " artifact:" + moduleName + ":war exploded\n" + + " META-INF/\n" + + " file:" + descriptor.getPath() + "\n"); + assertRunConfigurationCreated(artifact); + } + + @NotNull + private VirtualFile assertFileExist(String relativePath) { + VirtualFile file = getContentRoot().findFileByRelativePath(relativePath); + assertNotNull("File not found: " + relativePath, file); + return file; + } +} diff --git a/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java new file mode 100644 index 0000000000..e0997fb4e1 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java @@ -0,0 +1,149 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.compiler.artifacts; + +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.packaging.artifacts.ModifiableArtifactModel; +import com.intellij.packaging.elements.CompositePackagingElement; +import com.intellij.packaging.elements.PackagingElement; +import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.packaging.elements.PackagingElementResolvingContext; +import com.intellij.packaging.impl.elements.ArchivePackagingElement; +import com.intellij.packaging.impl.elements.DirectoryPackagingElement; +import com.intellij.packaging.impl.elements.ManifestFileUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +/** + * @author nik + * + * todo: this class is copied from compiler-impl tests. We need to extract compiler tests framework to a separate artifact and use it instead. + */ +public class ArtifactsTestUtil { + public static String printToString(PackagingElement element, int level) { + StringBuilder builder = new StringBuilder(StringUtil.repeatSymbol(' ', level)); + if (element instanceof ArchivePackagingElement) { + builder.append(((ArchivePackagingElement) element).getArchiveFileName()); + } else if (element instanceof DirectoryPackagingElement) { + builder.append(((DirectoryPackagingElement) element).getDirectoryName()).append("/"); + } else { + builder.append(element.toString()); + } + builder.append("\n"); + if (element instanceof CompositePackagingElement) { + for (PackagingElement child : ((CompositePackagingElement) element).getChildren()) { + builder.append(printToString(child, level + 1)); + } + } + return builder.toString(); + } + + public static void assertLayout(PackagingElement element, String expected) { + assertEquals(adjustMultiLine(expected), printToString(element, 0)); + } + + private static String adjustMultiLine(String expected) { + final List strings = StringUtil.split(StringUtil.trimStart(expected, "\n"), "\n"); + int min = Integer.MAX_VALUE; + for (String s : strings) { + int k = 0; + while (k < s.length() && s.charAt(k) == ' ') { + k++; + } + min = Math.min(min, k); + } + List lines = new ArrayList(); + for (String s : strings) { + lines.add(s.substring(min)); + } + return StringUtil.join(lines, "\n") + "\n"; + } + + public static void assertLayout(Project project, String artifactName, String expected) { + assertLayout(findArtifact(project, artifactName).getRootElement(), expected); + } + + public static void assertOutputPath(Project project, String artifactName, String expected) { + assertEquals(expected, findArtifact(project, artifactName).getOutputPath()); + } + + public static void assertOutputFileName(Project project, String artifactName, String expected) { + assertEquals(expected, findArtifact(project, artifactName).getRootElement().getName()); + } + + public static void setOutput(final Project project, final String artifactName, final String outputPath) { + new WriteAction() { + @Override + protected void run(@NotNull final Result result) { + final ModifiableArtifactModel model = ArtifactManager.getInstance(project).createModifiableModel(); + model.getOrCreateModifiableArtifact(findArtifact(project, artifactName)).setOutputPath(outputPath); + model.commit(); + } + }.execute(); + } + + public static void addArtifactToLayout(final Project project, final Artifact parent, final Artifact toAdd) { + new WriteAction() { + @Override + protected void run(@NotNull final Result result) { + final ModifiableArtifactModel model = ArtifactManager.getInstance(project).createModifiableModel(); + final PackagingElement artifactElement = PackagingElementFactory.getInstance().createArtifactElement(toAdd, project); + model.getOrCreateModifiableArtifact(parent).getRootElement().addOrFindChild(artifactElement); + model.commit(); + } + }.execute(); + } + + public static Artifact findArtifact(Project project, String artifactName) { + final ArtifactManager manager = ArtifactManager.getInstance(project); + final Artifact artifact = manager.findArtifact(artifactName); + assertNotNull("'" + artifactName + "' artifact not found", artifact); + return artifact; + } + + public static void assertManifest(Artifact artifact, PackagingElementResolvingContext context, @Nullable String mainClass, @Nullable String classpath) { + final CompositePackagingElement rootElement = artifact.getRootElement(); + final ArtifactType type = artifact.getArtifactType(); + assertManifest(rootElement, context, type, mainClass, classpath); + } + + public static void assertManifest(CompositePackagingElement rootElement, + PackagingElementResolvingContext context, + ArtifactType type, + @Nullable String mainClass, @Nullable String classpath) { + final VirtualFile file = ManifestFileUtil.findManifestFile(rootElement, context, type); + assertNotNull(file); + final Manifest manifest = ManifestFileUtil.readManifest(file); + assertEquals(mainClass, manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS)); + assertEquals(classpath, manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH)); + } + +} diff --git a/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/javaee/supportProvider/JavaeeFrameworkSupportProviderTestCase.java b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/javaee/supportProvider/JavaeeFrameworkSupportProviderTestCase.java new file mode 100644 index 0000000000..29d8c7fe66 --- /dev/null +++ b/google-cloud-tools-plugin/ultimate/testSrc/com/intellij/javaee/supportProvider/JavaeeFrameworkSupportProviderTestCase.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.javaee.supportProvider; + +import com.intellij.ide.util.frameworkSupport.FrameworkSupportProviderTestCase; +import com.intellij.javaee.appServerIntegrations.ApplicationServer; +import com.intellij.javaee.module.components.FrameworkVirtualFileSystem; +import com.intellij.javaee.serverInstances.ApplicationServersManager; +import com.intellij.openapi.application.ApplicationManager; + +import java.util.List; + +//todo: this class is copied from javaee_tests module. We need to create a separate artifact from that module and use it instead. +public abstract class JavaeeFrameworkSupportProviderTestCase extends FrameworkSupportProviderTestCase { + public static void deleteApplicationServers() { + final ApplicationServersManager manager = ApplicationServersManager.getInstance(); + final List servers = manager.getApplicationServers(); + final ApplicationServersManager.ApplicationServersManagerModifiableModel model = manager.createModifiableModel(); + for (ApplicationServer server : servers) { + model.deleteApplicationServer(server); + } + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + model.commit(); + } + }); + } + + + @Override + protected void tearDown() throws Exception { + FrameworkVirtualFileSystem.getJ2EEInstance().cleanup(); + deleteApplicationServers(); + super.tearDown(); + } +} diff --git a/gradle.properties b/gradle.properties index c690b34c4f..7911b79bb9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,8 @@ # limitations under the License. # -ideaVersion = IC-15.0.6 +ideaEdition = IC +ideaVersion = 15.0.6 javaVersion = 1.7 ijPluginRepoChannel = alpha version = 0.9.8-beta-SNAPSHOT diff --git a/settings.gradle b/settings.gradle index 459ebf1f89..6cd094ba42 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,4 +14,11 @@ * limitations under the License. */ -include 'google-account-plugin', 'google-cloud-tools-plugin', 'common-lib', 'common-test-lib' +include 'google-account-plugin' +include 'google-cloud-tools-plugin' +include 'common-lib' +include 'common-test-lib' + +include 'google-cloud-tools-plugin:jps-plugin' +include 'google-cloud-tools-plugin:runtime' +include 'google-cloud-tools-plugin:ultimate'