Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
runtime="true"
runtimeTypeId="com.google.cloud.tools.eclipse.appengine.standard.runtime"
startBeforePublish="false"
supportsRemoteHosts="true">
supportsRemoteHosts="true"
synchronousStart="true">
</serverType>
</extension>

Expand Down Expand Up @@ -322,4 +323,13 @@
id="com.google.cloud.tools.eclipse.appengine.localserver.cloudSdkDebugTarget">
</debugModelPresentation>
</extension>
<extension
point="org.eclipse.debug.core.statusHandlers">
<statusHandler
class="com.google.cloud.tools.eclipse.appengine.localserver.ui.StaleResourcesStatusHandler"
code="255"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment for "255" might be helpful

id="com.google.cloud.tools.eclipse.appengine.localserver.statusHandlers.staleResources"
plugin="com.google.cloud.tools.eclipse.appengine.localserver">
</statusHandler>
</extension>
</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ CANNOT_DETERMINE_EXECUTION_CONTEXT=Cannot determine server execution context
SERVER_ALREADY_RUNNING=Server is already running
SERVER_ALREADY_RUNNING_IN_MODE=Server is already running in "{0}" mode
UNABLE_TO_LAUNCH=Unable to launch App Engine server
STALE_RESOURCES_DETECTED=Stale resources detected
STALE_RESOURCES_LAUNCH_CONFIRMATION=Some recently changed resources are not yet published. Continue with launch?

target.terminated=<terminated>{0}
cloudsdk.server.description=Google Cloud SDK Dev App Server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ public final void setModulePublishState2(IModule[] module, int state) {
setModulePublishState(module, state);
}

@Override
protected void publishFinish(IProgressMonitor monitor) throws CoreException {
boolean allPublished = true;
IServer server = getServer();
IModule[] modules = server.getModules();
for (IModule module : modules) {
if (server.getModulePublishState(new IModule[] {module}) != IServer.PUBLISH_STATE_NONE) {
allPublished = false;
}
}
if (allPublished) {
setServerPublishState(IServer.PUBLISH_STATE_NONE);
}
}

private static IStatus newErrorStatus(String message) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.cloud.tools.eclipse.appengine.localserver.Messages;
import com.google.cloud.tools.eclipse.appengine.localserver.PreferencesInitializer;
import com.google.cloud.tools.eclipse.appengine.localserver.ui.LocalAppEngineConsole;
import com.google.cloud.tools.eclipse.appengine.localserver.ui.StaleResourcesStatusHandler;
import com.google.cloud.tools.eclipse.ui.util.MessageConsoleUtilities;
import com.google.cloud.tools.eclipse.ui.util.WorkbenchUtil;
import com.google.cloud.tools.eclipse.usagetracker.AnalyticsEvents;
Expand All @@ -50,13 +51,16 @@
import java.util.logging.Logger;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
Expand All @@ -65,6 +69,7 @@
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.ILaunchesListener2;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.DebugElement;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
Expand All @@ -82,6 +87,7 @@
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerListener;
import org.eclipse.wst.server.core.ServerCore;
import org.eclipse.wst.server.core.ServerEvent;
import org.eclipse.wst.server.core.ServerUtil;

Expand Down Expand Up @@ -128,6 +134,65 @@ public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws
return super.getLaunch(configuration, mode);
}

@Override
public boolean finalLaunchCheck(ILaunchConfiguration configuration, String mode,
IProgressMonitor monitor) throws CoreException {
SubMonitor progress = SubMonitor.convert(monitor, 40);
if (!super.finalLaunchCheck(configuration, mode, progress.newChild(20))) {
return false;
}

// If we're auto-publishing before launch, check if there may be stale
// resources not yet published. See
// https://github.com/GoogleCloudPlatform/google-cloud-eclipse/issues/1832
if (ServerCore.isAutoPublishing() && ResourcesPlugin.getWorkspace().isAutoBuilding()) {
// Must wait for any current autobuild to complete so resource changes are triggered
// and WTP will kick off ResourceChangeJobs. Note that there may be builds
// pending that are unrelated to our resource changes, so simply checking
// <code>JobManager.find(FAMILY_AUTO_BUILD).length > 0</code> produces too many
// false positives.
try {
Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, progress.newChild(20));
} catch (InterruptedException ex) {
/* ignore */
}
IServer server = ServerUtil.getServer(configuration);
if (server.shouldPublish() || hasPendingChangesToPublish()) {
IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
if (prompter != null) {
Object continueLaunch = prompter
.handleStatus(StaleResourcesStatusHandler.CONTINUE_LAUNCH_REQUEST, configuration);
if (!(Boolean) continueLaunch) {
// cancel the launch so Server.StartJob won't raise an error dialog, since the
// server won't have been started
monitor.setCanceled(true);
return false;
}
return true;
}
}
}
return true;
}

/**
* Check if there are pending changes to be published: this is a nasty condition that can occur if
* the user saves changes as part of launching the server.
*/
private boolean hasPendingChangesToPublish() {
Copy link
Contributor

@chanseokoh chanseokoh Jun 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering if this method can be called before some ResourceChangeJob is actually scheduled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It may not… It should be safe to join on the AUTOBUILD job.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. However, I'm fine with the current status if that turns out too complex or not really worth.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After experimenting, we need to join on the auto build job to ensure the workspace's resource changes happen, so that we see that there are pending events for publishing.

Job[] serverJobs = Job.getJobManager().find(ServerUtil.SERVER_JOB_FAMILY);
Job currentJob = Job.getJobManager().currentJob();
for (Job job : serverJobs) {
// Launching from Server#start() means this will be running within a
// Server.StartJob. All other jobs should be ResourceChangeJob or
// PublishJob, both of which indicate unpublished changes.
if (job != currentJob) {
return true;
}
}
return false;
}

@VisibleForTesting
void checkConflictingLaunches(ILaunchConfigurationType launchConfigType,
DefaultRunConfiguration runConfig, ILaunch[] launches) throws CoreException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2017 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.Messages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;

/**
* A simple prompter for the Platform/Debug framework to prompt the user to confirm whether the
* launch should continue when possibly stale resources have been found.
*/
public class StaleResourcesStatusHandler implements IStatusHandler {
/**
* The error code indicating that there may be stale resources. Used with the plugin ID to
* uniquely identify this prompter.
*/
static final int CONFIRM_LAUNCH_CODE = 255;

/**
* A specially crafted status message that is pass into the Debug Prompter class to obtain our
* confirmation prompter.
*/
public static final IStatus CONTINUE_LAUNCH_REQUEST = new Status(IStatus.INFO,
"com.google.cloud.tools.eclipse.appengine.localserver", CONFIRM_LAUNCH_CODE, "", null);

@Override
public Object handleStatus(IStatus status, Object source) throws CoreException {
if (source instanceof ILaunchConfiguration) {
ILaunchConfiguration config = (ILaunchConfiguration) source;
if (DebugUITools.isPrivate(config)) {
return Boolean.FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does a "private" config mean? So, if it's private, does this stop launching?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I gather, it's used for launches that aren't visible to the user. Like Ant builds or Maven builds. We could conceivably usr launches for our staging, etc. The advantage being that it's tracked and managed by the launch info.

}
}

Shell activeShell = DebugUIPlugin.getShell();
// should consider using MessageDialogWithToggle?
return MessageDialog.openQuestion(activeShell, Messages.getString("STALE_RESOURCES_DETECTED"),
Messages.getString("STALE_RESOURCES_LAUNCH_CONFIRMATION"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public static void setUp() throws Exception {
@After
public void tearDown() {
if (project != null) {
// close editors, so no property changes are dispatched on delete
bot.closeAllEditors();

// ensure there are no jobs
SwtBotWorkbenchActions.waitForProjects(bot, project);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.cloud.tools.eclipse.swtbot.SwtBotProjectActions;
import com.google.cloud.tools.eclipse.swtbot.SwtBotTestingUtilities;
import com.google.cloud.tools.eclipse.swtbot.SwtBotTreeUtilities;
import com.google.cloud.tools.eclipse.test.util.ThreadDumpingWatchdog;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Tree;
Expand All @@ -38,18 +44,12 @@
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.osgi.service.prefs.Preferences;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
* Create a native App Engine Standard project, launch in debug mode, verify working, and then
* terminate.
Expand Down Expand Up @@ -101,16 +101,21 @@ public void run() {

SWTBotTree launchTree =
new SWTBotTree(bot.widget(widgetOfType(Tree.class), debugView.getWidget()));

// avoid any stray processes that may be lying around
launchTree.contextMenu("Remove All Terminated").click();

SwtBotTreeUtilities.waitUntilTreeHasItems(bot, launchTree);
SWTBotTreeItem[] allItems = launchTree.getAllItems();
SwtBotTreeUtilities.waitUntilTreeHasText(bot, allItems[0]);
assertTrue("No App Engine launch found",
allItems[0].getText().contains("App Engine Standard at localhost"));
assertThat("No App Engine launch found", allItems[0].getText(),
Matchers.containsString("App Engine Standard at localhost"));

SWTBotView consoleView = bot.viewById("org.eclipse.ui.console.ConsoleView"); // IConsoleConstants.ID_CONSOLE_VIEW
consoleView.show();
assertTrue("App Engine console not active", consoleView.getViewReference()
.getContentDescription().contains("App Engine Standard at localhost"));
assertThat("App Engine console not active",
consoleView.getViewReference().getContentDescription(),
Matchers.containsString("App Engine Standard at localhost"));
final SWTBotStyledText consoleContents =
new SWTBotStyledText(bot.widget(widgetOfType(StyledText.class), consoleView.getWidget()));
SwtBotTestingUtilities.waitUntilStyledTextContains(bot,
Expand Down