/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.gdt.eclipse.suite.launch;
import com.google.gdt.eclipse.core.NetworkUtilities;
import com.google.gdt.eclipse.core.WebAppUtilities;
import com.google.gdt.eclipse.core.extensions.ExtensionQuery;
import com.google.gdt.eclipse.core.launch.LaunchConfigurationProcessorUtilities;
import com.google.gdt.eclipse.core.launch.WebAppLaunchConfiguration;
import com.google.gdt.eclipse.platform.launch.WtpPublisher;
import com.google.gdt.eclipse.suite.GdtPlugin;
import com.google.gdt.eclipse.suite.launch.processors.WarArgumentProcessor;
import com.google.gdt.eclipse.suite.launch.processors.WarArgumentProcessor.WarParser;
import com.google.gwt.eclipse.core.GWTPluginLog;
import com.google.gwt.eclipse.core.launch.processors.NoServerArgumentProcessor;
import com.google.gwt.eclipse.core.launch.processors.RemoteUiArgumentProcessor;
import com.google.gwt.eclipse.core.launch.processors.SuperDevModeArgumentProcessor;
import com.google.gwt.eclipse.oophm.model.WebAppDebugModel;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.JavaLaunchDelegate;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.ServerUtil;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Launch delegate for webapps.
*/
@SuppressWarnings({ "restriction", "nls" })
public class WebAppLaunchDelegate extends JavaLaunchDelegate {
/**
* Publish any {@link IModule}s that the project has if it is not using a managed war directory and it is running a
* server.
*/
public static void maybePublishModulesToWarDirectory(ILaunchConfiguration configuration, IProgressMonitor monitor,
IJavaProject javaProject, boolean forceFullPublish) throws CoreException {
if (javaProject == null) {
// No Java Project
return;
}
IProject project = javaProject.getProject();
List<String> args = LaunchConfigurationProcessorUtilities.parseProgramArgs(configuration);
if (WebAppUtilities.hasManagedWarOut(project) || NoServerArgumentProcessor.hasNoServerArg(args)) {
// Project has a managed war directory or it is running in noserver
// mode
return;
}
WarParser parser = WarArgumentProcessor.WarParser.parse(args, javaProject);
if (parser.resolvedUnverifiedWarDir == null) {
// Invalid war directory
return;
}
IModule[] modules = ServerUtil.getModules(project);
if (modules.length > 0) {
Path unmanagedWarPath = new Path(parser.resolvedUnverifiedWarDir);
WtpPublisher.publishModulesToWarDirectory(project, modules, unmanagedWarPath, forceFullPublish, monitor);
}
}
@Override
public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor)
throws CoreException {
ExtensionQuery<IBuildForLaunchCallback> extQuery = new ExtensionQuery<IBuildForLaunchCallback>(GdtPlugin.PLUGIN_ID,
"buildForLaunchCallback", "class");
List<ExtensionQuery.Data<IBuildForLaunchCallback>> buildBeforeLaunchCallbacks = extQuery.getData();
for (ExtensionQuery.Data<IBuildForLaunchCallback> callback : buildBeforeLaunchCallbacks) {
callback.getExtensionPointData().buildForLaunch(configuration, mode, monitor);
}
return super.buildForLaunch(configuration, mode, monitor);
}
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)
throws CoreException {
if (!addVmArgs(configuration)) {
return;
}
try {
if (!ensureWarArgumentExistenceInCertainCases(configuration)) {
return;
}
IJavaProject javaProject = getJavaProject(configuration);
maybePublishModulesToWarDirectory(configuration, monitor, javaProject, false);
} catch (Throwable t) {
// Play safely and continue launch
GdtPlugin.getLogger().logError(t, "Could not ensure WAR argument existence for the unmanaged WAR project.");
}
// explicitly ask the user to continue if the port is not available
if (!promptUserToContinueIfPortNotAvailable(configuration)) {
return;
}
// all of the program args attributes
List<String> args = null;
if (launch != null) {
try {
args = LaunchConfigurationProcessorUtilities.parseProgramArgs(launch.getLaunchConfiguration());
} catch (CoreException e) {
GWTPluginLog.logError(e);
}
}
// Do not start dev mode hooks if using -superDevMode arg
if (args != null && args.contains(RemoteUiArgumentProcessor.ARG_REMOTE_UI)
&& !args.contains(SuperDevModeArgumentProcessor.SUPERDEVMODE_ENABLED_ARG)) {
/*
* Add the launch to the DevMode view. This is tightly coupled because at the time of ILaunchListener's changed
* callback, the launch's process does not have a command-line set. Unfortunately there isn't another listener to
* solve our needs, so we add this glue here.
*/
WebAppDebugModel.getInstance().addOrReturnExistingLaunchConfiguration(launch, null, null);
}
super.launch(configuration, mode, launch, monitor);
}
@Override
protected File getDefaultWorkingDirectory(ILaunchConfiguration configuration) throws CoreException {
IJavaProject javaProject = getJavaProject(configuration);
if (javaProject != null && WarArgumentProcessor.doesMainTypeTakeWarArgument(configuration)) {
WarParser warParser = WarArgumentProcessor.WarParser
.parse(LaunchConfigurationProcessorUtilities.parseProgramArgs(configuration), javaProject);
if (warParser.isWarDirValid || warParser.isSpecifiedWithWarArg) {
return new File(warParser.resolvedUnverifiedWarDir);
}
}
// Fallback on super's implementation
return super.getDefaultWorkingDirectory(configuration);
}
/**
* Returns <code>true</code> if there are any problems with a severity level greater than or equal to error. Note that
* by default {@link JavaLaunchDelegate} only considers java problems to be launch problems. However, we want GWT
* errors to also be considered launch problems.
*/
@Override
protected boolean isLaunchProblem(IMarker problemMarker) throws CoreException {
Integer severity = (Integer) problemMarker.getAttribute(IMarker.SEVERITY);
if (severity != null) {
return severity.intValue() >= IMarker.SEVERITY_ERROR;
}
return false;
}
/**
* Check to see if the user wants to launch on a specific port and check if that port is available for use. If it is
* not, ask the user if they want to cancel the launch or continue anyway
*
* Visible for testing
*
* @param configuration
* A Launch Configuration
* @return true if launch should continue, false if user terminated
* @throws CoreException
*/
boolean promptUserToContinueIfPortNotAvailable(ILaunchConfiguration configuration) throws CoreException {
// ignore the auto select case
if (WebAppLaunchConfiguration.getAutoPortSelection(configuration)) {
return true;
}
// check to see if the port is available for the web app to launch
// allows user to trigger launch cancellation
final AtomicBoolean continueLaunch = new AtomicBoolean(true);
final String port = WebAppLaunchConfiguration.getServerPort(configuration);
if (!NetworkUtilities.isPortAvailable(port)) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
continueLaunch.set(MessageDialog.openQuestion(null, "Port in Use",
"The port " + port + " appears to be in use (perhaps by another launch), "
+ "do you still want to continue with this launch?"));
}
});
}
return continueLaunch.get();
}
/**
* @return Returns {@code}false if unsuccessful in adding the VM arguments and the launch should be cancelled.
*/
@Deprecated
private boolean addVmArgs(ILaunchConfiguration configuration) throws CoreException {
IProject project = getJavaProject(configuration).getProject();
ILaunchConfigurationWorkingCopy workingCopy = configuration.getWorkingCopy();
String vmArgs = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "");
return true;
}
/**
* In the case of a project with an unmanaged WAR directory or when the project is not a web app but the main type
* takes a WAR argument, we need to check at launch-time whether the launch config has a runtime WAR. If it does not,
* then we ask the user for one and insert it into the launch config.
*
* @param configuration
* the launch configuration that may be written to
* @return true to continue the launch, false to abort silently
* @throws CoreException
*/
private boolean ensureWarArgumentExistenceInCertainCases(ILaunchConfiguration configuration) throws CoreException {
IJavaProject javaProject = getJavaProject(configuration);
if (javaProject != null) {
IProject project = javaProject.getProject();
boolean isWebApp = WebAppUtilities.isWebApp(project);
if ((isWebApp && !WebAppUtilities.hasManagedWarOut(project))
|| (!isWebApp && WarArgumentProcessor.doesMainTypeTakeWarArgument(configuration))) {
List<String> args = LaunchConfigurationProcessorUtilities.parseProgramArgs(configuration);
WarParser parser = WarArgumentProcessor.WarParser.parse(args, javaProject);
if (!(parser.isSpecifiedWithWarArg || parser.isWarDirValid)) {
// The project's output WAR dir is unknown, so ask the user
IPath warDir = WebAppUtilities.getWarOutLocationOrPrompt(project);
if (warDir == null) {
return false;
}
// The processor will update to the proper argument style
// for the
// current project nature(s)
WarArgumentProcessor warArgProcessor = new WarArgumentProcessor();
warArgProcessor.setWarDirFromLaunchConfigCreation(warDir.toOSString());
ILaunchConfigurationWorkingCopy wc = configuration.getWorkingCopy();
LaunchConfigurationProcessorUtilities.updateViaProcessor(warArgProcessor, wc);
wc.doSave();
}
}
}
return true;
}
}