@@ -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 extends AppEngineSdk> 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 extends AppEngineSdk> 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 extends AppEngineSdk> 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 extends AppEngineSdk> 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 extends AppEngineSdk> 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 extends AppEngineSdk> 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 extends GwtDevModeServer> 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'