From 4f11a2259652735162d8d980cb96818c64731b24 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Thu, 8 Dec 2016 16:56:22 -0500 Subject: [PATCH] Implements _Launch on App Engine_ (#1070) --- .../StandardDeployCommandHandlerTest.java | 22 +-- .../META-INF/MANIFEST.MF | 1 + .../localserver/EclipseContextHolder.java | 3 +- .../LocalAppEngineServerDelegateTest.java | 5 +- .../LaunchAppEngineStandardHandlerTest.java | 154 ++++++++++++++++ .../localserver/ui/ServerTracker.java | 67 +++++++ .../plugin.xml | 42 +++++ .../server/LocalAppEngineServerDelegate.java | 5 + ...gineServerLaunchConfigurationDelegate.java | 3 + .../localserver/ui/AppEngineTabGroup.java | 5 +- .../ui/LaunchAppEngineStandardHandler.java | 166 ++++++++++++++++++ .../appengine/localserver/ui/LaunchModes.java | 48 +++++ .../localserver/ui/ServerPortExtension.java | 6 +- .../META-INF/MANIFEST.MF | 11 +- .../test/util/project/TestProjectCreator.java | 37 ++++ .../test/util/ui/ExecutionEventBuilder.java | 59 +++++++ .../tools/eclipse/util/status/StatusUtil.java | 58 +++--- 17 files changed, 632 insertions(+), 60 deletions(-) create mode 100644 plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandlerTest.java create mode 100644 plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerTracker.java create mode 100644 plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandler.java create mode 100644 plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchModes.java create mode 100644 plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/ui/ExecutionEventBuilder.java diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.deploy.ui.test/src/com/google/cloud/tools/eclipse/appengine/deploy/ui/standard/StandardDeployCommandHandlerTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.deploy.ui.test/src/com/google/cloud/tools/eclipse/appengine/deploy/ui/standard/StandardDeployCommandHandlerTest.java index e5d233dc8a..d77527a834 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.deploy.ui.test/src/com/google/cloud/tools/eclipse/appengine/deploy/ui/standard/StandardDeployCommandHandlerTest.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.deploy.ui.test/src/com/google/cloud/tools/eclipse/appengine/deploy/ui/standard/StandardDeployCommandHandlerTest.java @@ -20,26 +20,20 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.Collections; - +import com.google.cloud.tools.eclipse.test.util.ui.ExecutionEventBuilder; +import com.google.cloud.tools.eclipse.util.FacetedProjectHelper; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; -import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.ISources; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import com.google.cloud.tools.eclipse.appengine.deploy.ui.standard.StandardDeployCommandHandler; -import com.google.cloud.tools.eclipse.util.FacetedProjectHelper; - @RunWith(MockitoJUnitRunner.class) public class StandardDeployCommandHandlerTest { @@ -50,7 +44,8 @@ public void testExecute_facetedProjectCreationThrowsException() throws Execution StandardDeployCommandHandler handler = new StandardDeployCommandHandler(facetedProjectHelper); when(facetedProjectHelper.getFacetedProject(isA(IProject.class))).thenThrow(getFakeCoreException()); - ExecutionEvent event = getTestExecutionEvent(mock(IProject.class)); + ExecutionEvent event = new ExecutionEventBuilder().withCurrentSelection(mock(IProject.class)) + .withActiveShell(mock(Shell.class)).build(); handler.execute(event); } @@ -63,13 +58,4 @@ private Status getFakeErrorStatus() { return new Status(IStatus.ERROR, "fakePluginId", "test exception"); } - private ExecutionEvent getTestExecutionEvent(Object project) { - IEvaluationContext context = mock(IEvaluationContext.class); - IStructuredSelection selection = mock(IStructuredSelection.class); - when(selection.size()).thenReturn(1); - when(selection.getFirstElement()).thenReturn(project); - when(context.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME)).thenReturn(selection); - when(context.getVariable(ISources.ACTIVE_SHELL_NAME)).thenReturn(mock(Shell.class)); - return new ExecutionEvent(null /*command */, Collections.EMPTY_MAP, null /* trigger */, context); - } } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/META-INF/MANIFEST.MF index e17851d191..788871e20f 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/META-INF/MANIFEST.MF +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/META-INF/MANIFEST.MF @@ -12,6 +12,7 @@ Require-Bundle: org.hamcrest;bundle-version="1.1.0", org.eclipse.jst.j2ee.web Import-Package: com.google.cloud.tools.eclipse.test.util.project, com.google.cloud.tools.eclipse.test.util.ui, + com.google.cloud.tools.eclipse.util.io, org.mockito;provider=google;version="1.10.19", org.mockito.runners;provider=google;version="1.10.19", org.mockito.stubbing;provider=google;version="1.10.19", diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/EclipseContextHolder.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/EclipseContextHolder.java index 270f199e6f..44d7d5436f 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/EclipseContextHolder.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/EclipseContextHolder.java @@ -18,11 +18,10 @@ import org.eclipse.e4.core.contexts.EclipseContextFactory; import org.eclipse.e4.core.contexts.IEclipseContext; -import org.junit.Rule; import org.junit.rules.ExternalResource; /** - * Helper class to be used with {@link Rule} to make tests easier that depend on + * Helper class to be used with {@link org.junit.Rule} to make tests easier that depend on * {@link IEclipseContext}. It provides an empty context that can be filled with object needed for * the test using the {@link #set(Class, Object)} method. *

diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegateTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegateTest.java index 213848ce1a..f4a892804d 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegateTest.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegateTest.java @@ -31,6 +31,7 @@ import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager; import org.eclipse.wst.server.core.IModule; +import org.eclipse.wst.server.core.IModuleType; import org.eclipse.wst.server.core.IRuntime; import org.eclipse.wst.server.core.IRuntimeWorkingCopy; import org.eclipse.wst.server.core.IServer; @@ -154,7 +155,7 @@ public void testGetChildModules_noModuleType(){ @Test public void testGetChildModules_nonWebModuleType(){ - ModuleType nonWebModuleType = new ModuleType("non-web", "1.0"); + IModuleType nonWebModuleType = ModuleType.getModuleType("non-web", "1.0"); when(module1.getModuleType()).thenReturn(nonWebModuleType); IModule[] childModules = delegate.getChildModules(new IModule[]{module1}); @@ -163,7 +164,7 @@ public void testGetChildModules_nonWebModuleType(){ @Test public void testGetChildModules_webModuleType() { - ModuleType webModuleType = new ModuleType("jst.web", "1.0"); + IModuleType webModuleType = ModuleType.getModuleType("jst.web", "1.0"); when(module1.getModuleType()).thenReturn(webModuleType); when(module1.getId()).thenReturn("module1"); when(module1.loadAdapter(IWebModule.class, null)).thenReturn(webModule1); diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandlerTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandlerTest.java new file mode 100644 index 0000000000..a7586fd5e0 --- /dev/null +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandlerTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016 Google Inc. + * + * 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.eclipse.appengine.localserver.ui; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.cloud.tools.eclipse.appengine.facets.AppEngineStandardFacet; +import com.google.cloud.tools.eclipse.test.util.project.TestProjectCreator; +import com.google.cloud.tools.eclipse.test.util.ui.ExecutionEventBuilder; +import com.google.common.collect.Lists; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jst.common.project.facet.core.JavaFacet; +import org.eclipse.jst.j2ee.web.project.facet.WebFacetUtils; +import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; +import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager; +import org.eclipse.wst.server.core.IModule; +import org.eclipse.wst.server.core.IServer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Tests for the {@link LaunchAppEngineStandardHandler}. + */ +@RunWith(MockitoJUnitRunner.class) +public class LaunchAppEngineStandardHandlerTest { + private static final IProjectFacetVersion APPENGINE_STANDARD_FACET_VERSION_1 = + ProjectFacetsManager.getProjectFacet(AppEngineStandardFacet.ID).getVersion("1"); + + @Rule + public ServerTracker tracker = new ServerTracker(); + + private LaunchAppEngineStandardHandler handler; + private IServer serverToReturn = null; + + @Rule + public TestProjectCreator appEngineStandardProject1 = + new TestProjectCreator().withFacetVersions(Lists.newArrayList(JavaFacet.VERSION_1_7, + WebFacetUtils.WEB_25, APPENGINE_STANDARD_FACET_VERSION_1)); + @Rule + public TestProjectCreator appEngineStandardProject2 = + new TestProjectCreator().withFacetVersions(Lists.newArrayList(JavaFacet.VERSION_1_7, + WebFacetUtils.WEB_25, APPENGINE_STANDARD_FACET_VERSION_1)); + + + @Before + public void setUp() { + handler = new LaunchAppEngineStandardHandler() { + @Override + protected void launch(IServer server, String launchMode, SubMonitor progress) + throws CoreException { + // do nothing + } + + @Override + protected IServer findExistingServer(IModule[] modules, SubMonitor progress) { + if (serverToReturn != null) { + return serverToReturn; + } + return super.findExistingServer(modules, progress); + } + }; + } + + + @Test + public void testWithDefaultModule() throws ExecutionException, CoreException { + IModule module1 = appEngineStandardProject1.getModule(); + + ExecutionEvent event = new ExecutionEventBuilder().withCurrentSelection(module1).build(); + handler.execute(event); + assertEquals("new server should have been created", 1, tracker.getServers().size()); + } + + @Test + public void testWithTwoModules() throws ExecutionException, CoreException { + appEngineStandardProject1.setAppEngineServiceId("default"); + IModule module1 = appEngineStandardProject1.getModule(); + appEngineStandardProject2.setAppEngineServiceId("other"); + IModule module2 = appEngineStandardProject2.getModule(); + + ExecutionEvent event = + new ExecutionEventBuilder().withCurrentSelection(module1, module2).build(); + handler.execute(event); + assertEquals("new server should have been created", 1, tracker.getServers().size()); + } + + @Test(expected = ExecutionException.class) + public void failsIfAlreadyLaunched() throws ExecutionException, CoreException { + IModule module1 = appEngineStandardProject1.getModule(); + + ExecutionEvent event = new ExecutionEventBuilder().withCurrentSelection(module1).build(); + serverToReturn = mock(IServer.class); + ILaunch launch = mock(ILaunch.class); + when(serverToReturn.getServerState()).thenReturn(IServer.STATE_STARTED); + when(serverToReturn.getLaunch()).thenReturn(launch); + when(launch.getLaunchMode()).thenReturn(ILaunchManager.DEBUG_MODE); + handler.execute(event); + } + + public void testInvariantToModuleOrder() throws ExecutionException, CoreException { + appEngineStandardProject1.setAppEngineServiceId("default"); + IModule module1 = appEngineStandardProject1.getModule(); + appEngineStandardProject2.setAppEngineServiceId("other"); + IModule module2 = appEngineStandardProject2.getModule(); + + ExecutionEvent event = + new ExecutionEventBuilder().withCurrentSelection(module1, module2).build(); + handler.execute(event); + assertEquals("new server should have been created", 1, tracker.getServers().size()); + + // because we don't actually launch the servers, we won't get an ExecutionException + ExecutionEvent swappedEvent = + new ExecutionEventBuilder().withCurrentSelection(module2, module1).build(); + handler.execute(swappedEvent); + assertEquals("no new servers should be created", 1, tracker.getServers().size()); + } + + @Test(expected = ExecutionException.class) + public void failsWithClashingServiceIds() throws ExecutionException, CoreException { + appEngineStandardProject1.setAppEngineServiceId("other"); + IModule module1 = appEngineStandardProject1.getModule(); + appEngineStandardProject2.setAppEngineServiceId("other"); + IModule module2 = appEngineStandardProject2.getModule(); + + ExecutionEvent event = + new ExecutionEventBuilder().withCurrentSelection(module1, module2).build(); + handler.execute(event); + } + +} diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerTracker.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerTracker.java new file mode 100644 index 0000000000..fd8018926d --- /dev/null +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver.test/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerTracker.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 Google Inc. + * + * 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.eclipse.appengine.localserver.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.wst.server.core.IServer; +import org.eclipse.wst.server.core.IServerLifecycleListener; +import org.eclipse.wst.server.core.ServerCore; +import org.junit.rules.ExternalResource; + +/** Track creation of WTP {@link IServer} instances and ensure they are deleted. */ +public class ServerTracker extends ExternalResource { + private List servers = Collections.synchronizedList(new ArrayList()); + + private IServerLifecycleListener lifecycleListener = new IServerLifecycleListener() { + @Override + public void serverAdded(IServer server) { + servers.add(server); + } + + @Override + public void serverChanged(IServer server) {} + + @Override + public void serverRemoved(IServer server) { + servers.remove(server); + } + }; + + @Override + protected void before() { + ServerCore.addServerLifecycleListener(lifecycleListener); + } + + public List getServers() { + return servers; + } + + @Override + protected void after() { + ServerCore.removeServerLifecycleListener(lifecycleListener); + for (IServer server : servers) { + try { + server.delete(); + } catch (CoreException ex) { + /* ignore */ + } + } + } +} diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml index dcce26974e..d64ee92d86 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml @@ -222,4 +222,46 @@ typeIds="com.google.cloud.tools.eclipse.appengine.standard.server"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegate.java index d81ad57c54..bee83e4a01 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegate.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerDelegate.java @@ -41,6 +41,11 @@ @SuppressWarnings("restriction") // For FacetUtil public class LocalAppEngineServerDelegate extends ServerDelegate { + public static final String RUNTIME_TYPE_ID = + "com.google.cloud.tools.eclipse.appengine.standard.runtime"; + public static final String SERVER_TYPE_ID = + "com.google.cloud.tools.eclipse.appengine.standard.server"; + private static final IModule[] EMPTY_MODULES = new IModule[0]; private static final String SERVLET_MODULE_FACET = "jst.web"; //$NON-NLS-1$ private static final String ATTR_APP_ENGINE_SERVER_MODULES = "app-engine-server-modules-list"; //$NON-NLS-1$ diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java index 1904b5599c..5e3ef59d02 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java @@ -71,6 +71,9 @@ public class LocalAppEngineServerLaunchConfigurationDelegate private final static Logger logger = Logger.getLogger(LocalAppEngineServerLaunchConfigurationDelegate.class.getName()); + public static final String[] SUPPORTED_LAUNCH_MODES = + {ILaunchManager.RUN_MODE, ILaunchManager.DEBUG_MODE}; + private static final String DEBUGGER_HOST = "localhost"; //$NON-NLS-1$ private static void validateCloudSdk() throws CoreException { diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/AppEngineTabGroup.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/AppEngineTabGroup.java index db6c1c7920..50af127e58 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/AppEngineTabGroup.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/AppEngineTabGroup.java @@ -16,6 +16,7 @@ package com.google.cloud.tools.eclipse.appengine.localserver.ui; +import com.google.cloud.tools.eclipse.appengine.localserver.server.LocalAppEngineServerDelegate; import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; import org.eclipse.debug.ui.EnvironmentTab; import org.eclipse.debug.ui.ILaunchConfigurationDialog; @@ -25,9 +26,7 @@ public class AppEngineTabGroup extends AbstractLaunchConfigurationTabGroup { - private static final String[] SERVER_TYPE_IDS = new String[]{ - "com.google.cloud.tools.eclipse.appengine.standard.server" - }; + private static final String[] SERVER_TYPE_IDS = {LocalAppEngineServerDelegate.SERVER_TYPE_ID}; @Override public void createTabs(ILaunchConfigurationDialog dialog, String mode) { diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandler.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandler.java new file mode 100644 index 0000000000..e24adac141 --- /dev/null +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchAppEngineStandardHandler.java @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * 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.eclipse.appengine.localserver.ui; + +import com.google.cloud.tools.eclipse.appengine.localserver.server.LocalAppEngineServerDelegate; +import com.google.cloud.tools.eclipse.util.AdapterUtil; +import com.google.cloud.tools.eclipse.util.status.StatusUtil; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.wst.server.core.IModule; +import org.eclipse.wst.server.core.IServer; +import org.eclipse.wst.server.core.IServerType; +import org.eclipse.wst.server.core.IServerWorkingCopy; +import org.eclipse.wst.server.core.ServerCore; +import org.eclipse.wst.server.core.ServerUtil; + +/** Find or create a server with the selected projects and launch it. */ +public class LaunchAppEngineStandardHandler extends AbstractHandler { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + String launchMode = event.getParameter("launchMode"); + if (launchMode == null) { + launchMode = ILaunchManager.DEBUG_MODE; + } + IModule[] modules = asModules(event); + SubMonitor progress = SubMonitor.convert(null, 10); + try { + IServer server = findExistingServer(modules, progress.newChild(3)); + if (server != null && isRunning(server)) { + ILaunch launch = server.getLaunch(); + Preconditions.checkNotNull(launch, "Running server should have a launch"); + String detail = launchMode.equals(launch.getLaunchMode()) + ? "Server is already running" + : MessageFormat.format("Server is already running in \"{0}\" mode", + launch.getLaunchMode()); + IStatus status = StatusUtil.info(this, + MessageFormat.format("\"{0}\" already running", server.getName())); + throw new ExecutionException(detail, new CoreException(status)); + } else if (server == null) { + server = createServer(modules, progress.newChild(3)); + } + launch(server, launchMode, progress.newChild(4)); + } catch (CoreException ex) { + throw new ExecutionException("Unable to configure server", ex); + } + return null; + } + + private static boolean isRunning(IServer server) { + return server.getServerState() == IServer.STATE_STARTED + || server.getServerState() == IServer.STATE_STARTING; + } + + @VisibleForTesting + protected IServer findExistingServer(IModule[] modules, SubMonitor progress) { + if (modules.length == 1) { + IServer defaultServer = ServerCore.getDefaultServer(modules[0]); + if (defaultServer != null && LocalAppEngineServerDelegate.SERVER_TYPE_ID + .equals(defaultServer.getServerType().getId())) { + return defaultServer; + } + } + Set myModules = ImmutableSet.copyOf(modules); + // Look for servers that contain these modules + // Could prioritize servers that have *exactly* these modules, + // or that have the smallest overlap + for (IServer server : ServerCore.getServers()) { + if (!LocalAppEngineServerDelegate.SERVER_TYPE_ID.equals(server.getServerType().getId())) { + continue; + } + Set serverModules = ImmutableSet.copyOf(server.getModules()); + if (Sets.intersection(myModules, serverModules).size() == myModules.size()) { + return server; + } + } + return null; + } + + private IServer createServer(IModule[] modules, SubMonitor progress) throws CoreException { + IServerType serverType = ServerCore.findServerType(LocalAppEngineServerDelegate.SERVER_TYPE_ID); + IServerWorkingCopy serverWorkingCopy = + serverType.createServer(null, null, progress.newChild(4)); + serverWorkingCopy.modifyModules(modules, null, progress.newChild(4)); + return serverWorkingCopy.save(false, progress.newChild(2)); + } + + @VisibleForTesting + protected void launch(IServer server, String launchMode, SubMonitor progress) + throws CoreException { + server.start(launchMode, progress); + } + + /** Identify the relevant modules from the execution context. */ + private static IModule[] asModules(ExecutionEvent event) throws ExecutionException { + // First check the current selected objects + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof IStructuredSelection && !selection.isEmpty()) { + Object[] selectedObjects = ((IStructuredSelection) selection).toArray(); + List modules = new ArrayList<>(selectedObjects.length); + for (Object object : selectedObjects) { + modules.add(asModule(object)); + } + return modules.toArray(new IModule[modules.size()]); + } + // Check the project of the active editor. + IEditorPart editor = HandlerUtil.getActiveEditor(event); + if (editor != null && editor.getEditorInput() instanceof IFileEditorInput) { + IFileEditorInput input = (IFileEditorInput) editor.getEditorInput(); + IProject project = input.getFile().getProject(); + if (project != null) { + return new IModule[] {asModule(project)}; + } + } + throw new ExecutionException("Cannot determine server execution context"); + } + + private static IModule asModule(Object object) throws ExecutionException { + IModule module = AdapterUtil.adapt(object, IModule.class); + if (module != null) { + return module; + } + IProject project = AdapterUtil.adapt(object, IProject.class); + if (project != null) { + module = ServerUtil.getModule(project); + if (module != null) { + return module; + } + } + throw new ExecutionException("no module found for " + object); + } + +} diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchModes.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchModes.java new file mode 100644 index 0000000000..7118f58415 --- /dev/null +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/LaunchModes.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Google Inc. + * + * 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.eclipse.appengine.localserver.ui; + +import com.google.cloud.tools.eclipse.appengine.localserver.server.LocalAppEngineServerLaunchConfigurationDelegate; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.commands.IParameterValues; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.ILaunchMode; + +/** + * A helper class for the Eclipse UI that provides completions for the parameters for + * LocalAppEngineServer*-supported launch modes. + */ +public class LaunchModes implements IParameterValues { + @Override + public Map getParameterValues() { + ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); + Map modes = new HashMap<>(); + for (String modeId : LocalAppEngineServerLaunchConfigurationDelegate.SUPPORTED_LAUNCH_MODES) { + ILaunchMode mode = manager.getLaunchMode(modeId); + if (mode != null) { + // label is intended to be shown in menus and buttons and often has + // embedded '&' for mnemonics, which isn't useful here + String label = mode.getLabel(); + label = label.replace("&", ""); + modes.put(label, mode.getIdentifier()); + } + } + return modes; + } +} diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerPortExtension.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerPortExtension.java index 4c05e65d62..c0d45a22b4 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerPortExtension.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/ServerPortExtension.java @@ -18,6 +18,7 @@ import com.google.cloud.tools.eclipse.appengine.localserver.Messages; import com.google.cloud.tools.eclipse.appengine.localserver.server.LocalAppEngineServerBehaviour; +import com.google.cloud.tools.eclipse.appengine.localserver.server.LocalAppEngineServerDelegate; import com.google.common.annotations.VisibleForTesting; import java.beans.PropertyChangeEvent; import org.eclipse.jface.fieldassist.ControlDecoration; @@ -38,9 +39,6 @@ */ public class ServerPortExtension extends ServerCreationWizardPageExtension { - private static final String APP_ENGINE_SERVER_TYPE_ID = - "com.google.cloud.tools.eclipse.appengine.standard.server"; //$NON-NLS-1$ - @VisibleForTesting Label portLabel; @VisibleForTesting Text portText; @VisibleForTesting ControlDecoration portDecoration; @@ -73,7 +71,7 @@ public void createControl(UI_POSITION position, Composite parent) { public void handlePropertyChanged(PropertyChangeEvent event) { if (event != null && event.getNewValue() instanceof IServerType) { IServerType serverType = (IServerType) event.getNewValue(); - boolean showPort = APP_ENGINE_SERVER_TYPE_ID.equals(serverType.getId()); + boolean showPort = LocalAppEngineServerDelegate.SERVER_TYPE_ID.equals(serverType.getId()); portLabel.setVisible(showPort); portText.setVisible(showPort); if (showPort) { diff --git a/plugins/com.google.cloud.tools.eclipse.test.util/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.test.util/META-INF/MANIFEST.MF index 811bd542e7..27b3a40fa6 100644 --- a/plugins/com.google.cloud.tools.eclipse.test.util/META-INF/MANIFEST.MF +++ b/plugins/com.google.cloud.tools.eclipse.test.util/META-INF/MANIFEST.MF @@ -7,9 +7,13 @@ Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy -Require-Bundle: org.junit;bundle-version="4.12.0" -Import-Package: com.google.common.base;version="20.0.0", +Require-Bundle: org.junit;bundle-version="4.12.0", + org.eclipse.ui.ide +Import-Package: com.google.cloud.tools.eclipse.appengine.facets, + com.google.common.base;version="20.0.0", javax.servlet, + org.eclipse.core.commands, + org.eclipse.core.expressions, org.eclipse.core.resources, org.eclipse.core.runtime;version="3.5.0", org.eclipse.core.runtime.jobs, @@ -19,11 +23,14 @@ Import-Package: com.google.common.base;version="20.0.0", org.eclipse.jetty.server;version="9.2.13", org.eclipse.jetty.server.handler;version="9.2.13", org.eclipse.jetty.util.component;version="9.2.13", + org.eclipse.jface.viewers, org.eclipse.swt.widgets, org.eclipse.wst.common.project.facet.core, org.eclipse.wst.common.project.facet.core.internal, org.eclipse.wst.common.project.facet.core.util.internal, + org.eclipse.wst.server.core, org.mockito;provider=google;version="1.10.19", + org.mockito.stubbing;provider=google;version="1.10.19", org.osgi.framework;version="1.8.0" Export-Package: com.google.cloud.tools.eclipse.test.util, com.google.cloud.tools.eclipse.test.util.http, diff --git a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java index 9ebfb7564c..f194a6fdf1 100644 --- a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java +++ b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java @@ -16,11 +16,18 @@ package com.google.cloud.tools.eclipse.test.util.project; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.cloud.tools.eclipse.appengine.facets.WebProjectUtil; import com.google.common.base.Strings; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.ResourcesPlugin; @@ -34,12 +41,15 @@ import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager; import org.eclipse.wst.common.project.facet.core.internal.FacetedProjectNature; +import org.eclipse.wst.server.core.IModule; +import org.eclipse.wst.server.core.ServerUtil; import org.junit.rules.ExternalResource; public final class TestProjectCreator extends ExternalResource { private IJavaProject javaProject; private String containerPath; + private String appEngineServiceId; private List projectFacetVersions = new ArrayList<>(); public TestProjectCreator withClasspathContainerPath(String containerPath) { @@ -52,6 +62,11 @@ public TestProjectCreator withFacetVersions(List projectFa return this; } + public TestProjectCreator withAppEngineServiceId(String serviceId) { + appEngineServiceId = serviceId; + return this; + } + @Override protected void before() throws Throwable { createJavaProject("test" + Math.random()); @@ -76,6 +91,10 @@ public IProject getProject() { return javaProject.getProject(); } + public IModule getModule() { + return ServerUtil.getModule(javaProject.getProject()); + } + private void createJavaProject(String projectName) throws CoreException, JavaModelException { IProjectDescription newProjectDescription = ResourcesPlugin.getWorkspace().newProjectDescription(projectName); newProjectDescription.setNatureIds(new String[]{JavaCore.NATURE_ID, FacetedProjectNature.NATURE_ID}); @@ -86,6 +105,9 @@ private void createJavaProject(String projectName) throws CoreException, JavaMod addContainerPathToRawClasspath(); addFacets(); + if (appEngineServiceId != null) { + setAppEngineServiceId(appEngineServiceId); + } } private void addContainerPathToRawClasspath() throws JavaModelException { @@ -106,4 +128,19 @@ private void addFacets() throws CoreException { } } } + + public void setAppEngineServiceId(String serviceId) throws CoreException { + IFolder webinf = WebProjectUtil.getWebInfDirectory(getProject()); + IFile descriptorFile = webinf.getFile("appengine-web.xml"); + assertTrue("Project should have AppEngine Standard facet", descriptorFile.exists()); + StringBuilder newAppEngineWebDescriptor = new StringBuilder(); + newAppEngineWebDescriptor + .append("\n"); + newAppEngineWebDescriptor.append("").append(serviceId).append("\n"); + newAppEngineWebDescriptor.append("\n"); + InputStream contents = new ByteArrayInputStream( + newAppEngineWebDescriptor.toString().getBytes(StandardCharsets.UTF_8)); + descriptorFile.setContents(contents, IFile.FORCE, null); + } + } diff --git a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/ui/ExecutionEventBuilder.java b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/ui/ExecutionEventBuilder.java new file mode 100644 index 0000000000..103ca8ea78 --- /dev/null +++ b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/ui/ExecutionEventBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google Inc. + * + * 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.eclipse.test.util.ui; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.ISources; + +/** + * Mocks up an {@link ExecutionEvent} object for use with Eclipse Commands/Handlers. + */ +public class ExecutionEventBuilder { + + private IEvaluationContext context; + + public ExecutionEventBuilder() { + context = mock(IEvaluationContext.class); + } + + public ExecutionEvent build() { + return new ExecutionEvent(null /* command */, Collections.EMPTY_MAP, null /* trigger */, + context); + } + + public ExecutionEventBuilder withActiveShell(Shell shell) { + when(context.getVariable(ISources.ACTIVE_SHELL_NAME)).thenReturn(shell); + return this; + } + + public ExecutionEventBuilder withCurrentSelection(Object... objects) { + return withCurrentSelection(new StructuredSelection(objects)); + } + + public ExecutionEventBuilder withCurrentSelection(IStructuredSelection selection) { + when(context.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME)).thenReturn(selection); + return this; + } +} diff --git a/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/status/StatusUtil.java b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/status/StatusUtil.java index da57988e38..f92e7eae16 100644 --- a/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/status/StatusUtil.java +++ b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/status/StatusUtil.java @@ -29,44 +29,44 @@ public class StatusUtil { private StatusUtil() {} - public static IStatus error(Class origin, String message) { - return error(origin, message, null); + public static IStatus error(Object origin, String message) { + return new Status(IStatus.ERROR, getBundleId(origin), message); } - public static IStatus error(Class origin, String message, Throwable error) { - String bundleOrClassname = null; - - Bundle bundle = FrameworkUtil.getBundle(origin); - if (bundle == null) { - bundleOrClassname = origin.getName(); - } else { - bundleOrClassname = bundle.getSymbolicName(); - } - return error(bundleOrClassname, message, error); + public static IStatus error(Object origin, String message, Throwable error) { + return new Status(IStatus.ERROR, getBundleId(origin), message, error); } - public static IStatus error(Object origin, String message) { - if (origin instanceof Class) { - return error((Class) origin, message); - } else { - return error(origin.getClass(), message); - } + public static IStatus warn(Object origin, String message) { + return new Status(IStatus.WARNING, getBundleId(origin), message); } - public static IStatus error(Object origin, String message, Throwable error) { - if (origin instanceof Class) { - return error((Class) origin, message, error); - } else { - return error(origin.getClass(), message, error); - } + public static IStatus warn(Object origin, String message, Throwable error) { + return new Status(IStatus.WARNING, getBundleId(origin), message, error); + } + + public static IStatus info(Object origin, String message) { + return new Status(IStatus.INFO, getBundleId(origin), message); + } + + public static IStatus info(Object origin, String message, Throwable error) { + return new Status(IStatus.INFO, getBundleId(origin), message, error); } - private static IStatus error(String bundleOrClassname, String message, Throwable error) { - if (error == null) { - return new Status(IStatus.ERROR, bundleOrClassname, message); + private static String getBundleId(Object origin) { + Class clazz = null; + if (origin == null) { + clazz = StatusUtil.class; + } else if (origin instanceof Class) { + clazz = (Class) origin; } else { - return new Status(IStatus.ERROR, bundleOrClassname, message, error); + clazz = origin.getClass(); } - } + Bundle bundle = FrameworkUtil.getBundle(clazz); + if (bundle == null) { + return clazz.getName(); // what else can we do? + } + return bundle.getSymbolicName(); + } }