/*******************************************************************************
* Copyright (c) 2012 - 2016 Pivotal Software, Inc.
* 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
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package com.vmware.vfabric.ide.eclipse.tcserver.internal.core;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.lang.StringUtils;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jst.server.tomcat.core.internal.FileUtil;
import org.eclipse.jst.server.tomcat.core.internal.Messages;
import org.eclipse.jst.server.tomcat.core.internal.PingThread;
import org.eclipse.jst.server.tomcat.core.internal.ProgressUtil;
import org.eclipse.jst.server.tomcat.core.internal.TomcatServerBehaviour;
import org.eclipse.jst.server.tomcat.core.internal.Trace;
import org.eclipse.mylyn.commons.net.WebLocation;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.ServerPort;
import org.eclipse.wst.server.core.model.IModuleResource;
import org.eclipse.wst.server.core.model.IModuleResourceDelta;
import com.vmware.vfabric.ide.eclipse.tcserver.internal.core.TcServer.Layout;
import com.vmware.vfabric.ide.eclipse.tcserver.reloading.TcServerReloadingPlugin;
/**
* @author Steffen Pingel
* @author Christian Dupuis
* @author Kris De Volder
* @author Leo Dos Santos
*/
public class TcServerBehaviour extends TomcatServerBehaviour {
private static class SslPingThread extends PingThread {
private static int DELAY = 10;
private static int INTERVAL = 250;
private final String url;
private boolean stopped;
private final TcServerBehaviour serverBehaviour;
private final int maxPings;
private int interval;
public SslPingThread(IServer server, String url, int maxPings, TcServerBehaviour behaviour) {
super(server, url, maxPings, behaviour);
this.url = url;
this.maxPings = (maxPings != -1) ? maxPings : 100;
this.serverBehaviour = behaviour;
}
@Override
protected void ping() {
interval = DELAY;
for (int i = 0; i < maxPings && !stopped; i++) {
try {
Thread.sleep(interval);
WebLocation location = new WebLocation(url);
HttpClient client = new HttpClient();
org.eclipse.mylyn.commons.net.WebUtil.configureHttpClient(client, ""); //$NON-NLS-1$
HeadMethod method = new HeadMethod(location.getUrl());
HostConfiguration hostConfiguration = org.eclipse.mylyn.commons.net.WebUtil
.createHostConfiguration(client, location, new NullProgressMonitor());
org.eclipse.mylyn.commons.net.WebUtil.execute(client, hostConfiguration, method,
new NullProgressMonitor());
// success
serverBehaviour.setServerStarted();
stop();
break;
}
catch (ConnectException e) {
// ignore
}
catch (Exception e) {
Trace.trace(Trace.SEVERE, "Failed to ping for tc Server startup.", e);
forceStop();
break;
}
interval = INTERVAL;
}
}
private void forceStop() {
stop();
serverBehaviour.stopImpl();
}
@Override
public void stop() {
super.stop();
this.stopped = true;
}
}
public static boolean mergeClasspathIfRequired(List<IRuntimeClasspathEntry> cp, IRuntimeClasspathEntry entry) {
return mergeClasspathIfRequired(cp, entry, false);
}
public static boolean mergeClasspathIfRequired(List<IRuntimeClasspathEntry> cp, IRuntimeClasspathEntry entry,
boolean prepend) {
boolean first = true;
Iterator<IRuntimeClasspathEntry> iterator = cp.iterator();
while (iterator.hasNext()) {
IRuntimeClasspathEntry entry2 = iterator.next();
if (entry2.getPath().equals(entry.getPath())) {
if (prepend && !first) {
// ensure new element is always first
iterator.remove();
}
else {
return false;
}
}
first = false;
}
if (prepend) {
cp.add(0, entry);
}
else {
cp.add(entry);
}
return true;
}
public String getDeployRoot() {
return getServerDeployDirectory().toOSString() + File.separator;
}
// make method visible to package
@Override
public IModuleResourceDelta[] getPublishedResourceDelta(IModule[] module) {
return super.getPublishedResourceDelta(module);
}
@Override
public IModuleResource[] getResources(IModule[] module) {
return super.getResources(module);
}
public IServicabilityInfo getServicabilityInfo() throws CoreException {
TcServer server = getTomcatServer();
return getTomcatConfiguration().getServicabilityInfo(server.getRuntimeBaseDirectory());
}
@Override
public TcServerConfiguration getTomcatConfiguration() throws CoreException {
return (TcServerConfiguration) super.getTomcatConfiguration();
}
@Override
public TcServerRuntime getTomcatRuntime() {
return (TcServerRuntime) super.getTomcatRuntime();
}
@Override
public TcServer getTomcatServer() {
return (TcServer) super.getTomcatServer();
}
@Override
public void setupLaunch(ILaunch launch, String launchMode, IProgressMonitor monitor) throws CoreException {
super.setupLaunch(launch, launchMode, monitor);
for (IModule[] module : getAllModules()) {
setModuleState(module, IServer.STATE_STARTING);
}
// transfer VM arguments to launch to make them accessible for later use
launch.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, launch.getLaunchConfiguration()
.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, (String) null));
TcServer.getCallback().setupLaunch(getTomcatServer(), launch, launchMode, monitor);
TcServerConfiguration configuration = getTomcatConfiguration();
if (configuration.getMainPort() == null && ping == null) {
ServerPort serverPort = configuration.getMainSslPort();
if (serverPort != null) {
try {
String url = "https://" + getServer().getHost() + ":" + serverPort.getPort();
ping = new SslPingThread(getServer(), url, -1, this);
}
catch (Exception e) {
Trace.trace(Trace.SEVERE, "Can't ping for tc Server startup.");
}
}
}
}
@Override
public void setupLaunchConfiguration(ILaunchConfigurationWorkingCopy workingCopy, IProgressMonitor monitor)
throws CoreException {
super.setupLaunchConfiguration(workingCopy, monitor);
TcServer.getCallback().setupLaunchConfiguration(getTomcatServer(), workingCopy, monitor);
if (getTomcatServer().isTestEnvironment()) {
setupRuntimeClasspathForTestEnvironment(workingCopy, monitor);
}
String existingVMArgs = workingCopy.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS,
(String) null);
String[] parsedVMArgs = null;
if (existingVMArgs != null) {
parsedVMArgs = DebugPlugin.parseArguments(existingVMArgs);
}
List<String> argsToAdd = new ArrayList<String>();
List<String> argsToRemove = new ArrayList<String>();
if (getTomcatServer().isAgentRedeployEnabled() && TcServerReloadingPlugin.getAgentJarPath() != null) {
argsToAdd.add("-javaagent:\"" + TcServerReloadingPlugin.getAgentJarPath() + "\"");
argsToAdd.add("-noverify");
String agentOptions = getTomcatServer().getAgentOptions();
if (StringUtils.isNotBlank(agentOptions)) {
argsToAdd.add("-Dspringloaded=\"" + agentOptions + "\"");
}
else {
argsToRemove.add("-Dspringloaded");
}
}
else {
argsToRemove.add("-javaagent:\"" + TcServerReloadingPlugin.getAgentJarPath() + "\"");
argsToRemove.add("-noverify");
argsToRemove.add("-Dspringloaded");
}
boolean addXmx = true;
boolean addXss = true;
boolean addMaxPermSize = true;
boolean addLogManager = true;
boolean addLogConfigFile = true;
// check if arguments are already present
if (parsedVMArgs != null) {
for (String parsedVMArg : parsedVMArgs) {
if (parsedVMArg.startsWith("-Xmx")) {
addXmx = false;
}
else if (parsedVMArg.startsWith("-Xss")) {
addXss = false;
}
else if (parsedVMArg.startsWith("-XX:MaxPermSize=")) {
addMaxPermSize = false;
}
else if (parsedVMArg.startsWith("-Djava.util.logging.manager")) {
addLogManager = false;
}
else if (parsedVMArg.startsWith("-Djava.util.logging.config.file")) {
addLogConfigFile = false;
}
}
}
if (addXmx) {
argsToAdd.add("-Xmx768m");
}
if (addXss) {
argsToAdd.add("-Xss256k");
}
if (addMaxPermSize) {
argsToAdd.add("-XX:MaxPermSize=256m");
}
if (addLogManager) {
argsToAdd.add(
"-Djava.util.logging.manager=com.springsource.tcserver.serviceability.logging.TcServerLogManager");
}
if (addLogConfigFile) {
argsToAdd.add("-Djava.util.logging.config.file=\"" + getTomcatServer()
.getInstanceBase(getServer().getRuntime()).append("conf").append("logging.properties") + "\"");
}
argsToAdd.addAll(getTomcatServer().getAddExtraVmArgs());
argsToRemove.addAll(getTomcatServer().getRemoveExtraVmArgs());
if (argsToAdd.size() > 0 || argsToRemove.size() > 0) {
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, mergeArguments(existingVMArgs,
argsToAdd.toArray(new String[0]), argsToRemove.toArray(new String[0]), false));
}
}
/**
* In test mode webtools creates a temporary directory that is missing
* required jars for tc Server. This method copies the necessary jars from
* the instance base directory.
*/
private void setupRuntimeClasspathForTestEnvironment(ILaunchConfigurationWorkingCopy launchConfiguration,
IProgressMonitor monitor) throws CoreException {
TcServer tcServer = getTomcatServer();
IRuntimeClasspathEntry[] originalClasspath = JavaRuntime.computeUnresolvedRuntimeClasspath(launchConfiguration);
List<IRuntimeClasspathEntry> cp = new ArrayList<IRuntimeClasspathEntry>(Arrays.asList(originalClasspath));
IPath instanceBaseDirectory = tcServer.getInstanceBase(getServer().getRuntime());
if (instanceBaseDirectory != null) {
boolean changed = false;
IPath path = instanceBaseDirectory.append("bin");
changed |= addJarToClasspath(launchConfiguration, cp, path, "tomcat-juli.jar", false);
if (changed) {
List<String> list = new ArrayList<String>(cp.size());
for (IRuntimeClasspathEntry entry : cp) {
try {
list.add(entry.getMemento());
}
catch (Exception e) {
Trace.trace(Trace.SEVERE, "Could not resolve classpath entry: " + entry, e);
}
}
launchConfiguration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH, list);
}
}
}
private boolean addJarToClasspath(ILaunchConfigurationWorkingCopy launchConfiguration,
List<IRuntimeClasspathEntry> cp, IPath path, String prefix, boolean prepend) throws CoreException {
File directory = path.toFile();
String[] filenames = directory.list();
if (filenames != null) {
for (String filename : filenames) {
if (filename.startsWith(prefix) && filename.endsWith(".jar")) {
IRuntimeClasspathEntry entry = JavaRuntime.newArchiveRuntimeClasspathEntry(path.append(filename));
return TcServerBehaviour.mergeClasspathIfRequired(cp, entry, prepend);
}
}
}
return false;
}
@Override
public void stop(boolean force) {
if (!force) {
ServerPort[] ports = getTomcatServer().getServerPorts();
for (ServerPort serverPort : ports) {
if ("server".equals(serverPort.getId())) {
super.stop(force);
return;
}
}
int state = getServer().getServerState();
// If stopped or stopping, no need to run stop command again
if (state == IServer.STATE_STOPPED || state == IServer.STATE_STOPPING) {
return;
}
for (IModule[] module : getAllModules()) {
int currentState = getServer().getModuleState(module);
if (currentState < IServer.STATE_STOPPING) {
setModuleState(module, IServer.STATE_STOPPING);
}
}
setServerState(IServer.STATE_STOPPING);
// fall-back to JMX command
ShutdownTcServerCommand command = new ShutdownTcServerCommand(this);
try {
command.execute();
// need to kill server unfortunately since the shutdown command
// only stops Catalina but not the Tomcat process itself
terminate();
stopImpl();
}
catch (TimeoutException e) {
// webtools will invoke this method again with and set force to
// true in case of a timeout
}
catch (CoreException e) {
// ignore, already logged in command
super.stop(true);
}
}
else {
super.stop(force);
}
}
@Override
protected void stopImpl() {
/*
* Set the stopped state for all modules
*/
for (IModule[] module : getAllModules()) {
setModuleState(module, IServer.STATE_STOPPED);
}
super.stopImpl();
}
/**
* Public for testing only.
*/
@Override
public String[] getRuntimeVMArguments() {
IPath instancePath = getRuntimeBaseDirectory();
IPath deployPath;
// If serving modules without publishing, use workspace path as the
// deploy path
if (getTomcatServer().isServeModulesWithoutPublish()) {
deployPath = ResourcesPlugin.getWorkspace().getRoot().getLocation();
}
// Else normal publishing for modules
else {
deployPath = getServerDeployDirectory();
// If deployPath is relative, convert to canonical path and hope for
// the best
if (!deployPath.isAbsolute()) {
try {
String deployLoc = (new File(deployPath.toOSString())).getCanonicalPath();
deployPath = new Path(deployLoc);
}
catch (IOException e) {
// Ignore if there is a problem
}
}
}
IPath installPath;
if (getTomcatServer().getLayout() == Layout.COMBINED) {
installPath = instancePath;
}
else {
installPath = getTomcatRuntime().getTomcatLocation();
}
// pass true to ensure that configPath is always respected
return getTomcatVersionHandler().getRuntimeVMArguments(installPath, instancePath, deployPath, true);
}
@Override
protected void publishModule(int kind, int deltaKind, IModule[] moduleTree, IProgressMonitor monitor)
throws CoreException {
super.publishModule(kind, deltaKind, moduleTree, monitor);
// reload application if enhanced redeploy is enabled
TcPublisher op = new TcPublisher(this, kind, moduleTree, deltaKind);
op.execute(monitor, null);
}
@Override
protected void publishServer(int kind, IProgressMonitor monitor) throws CoreException {
if (getServer().getRuntime() == null) {
return;
}
// set install dir to catalina.home
IPath installDir = getRuntimeBaseDirectory();
IPath confDir = null;
if (getTomcatServer().isTestEnvironment()) {
confDir = getRuntimeBaseDirectory();
IStatus status = getTomcatVersionHandler().prepareRuntimeDirectory(confDir);
if (status != null && !status.isOK()) {
throw new CoreException(status);
}
IPath instanceBase = getTomcatServer().getInstanceBase(getServer().getRuntime());
// copy keystore file in case of ssl instance
IPath keystorePath = instanceBase.append("conf").append("tcserver.keystore");
if (keystorePath.toFile().exists()) {
IPath destPath = confDir.append("conf");
if (!destPath.toFile().exists()) {
destPath.toFile().mkdirs();
}
File file = keystorePath.toFile();
destPath = destPath.append(file.getName());
if (!destPath.toFile().exists()) {
FileUtil.copyFile(file.getAbsolutePath(), destPath.toFile().getAbsolutePath());
}
}
// copy libraries from instance base
IPath libPath = instanceBase.append("lib");
if (libPath.toFile().exists()) {
File[] files = libPath.toFile().listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".jar")) {
IPath destPath = confDir.append("lib");
if (!destPath.toFile().exists()) {
destPath.toFile().mkdirs();
}
destPath = destPath.append(file.getName());
if (!destPath.toFile().exists()) {
FileUtil.copyFile(file.getAbsolutePath(), destPath.toFile().getAbsolutePath());
}
}
}
}
}
}
else {
confDir = installDir;
}
IStatus status = getTomcatVersionHandler().prepareDeployDirectory(getServerDeployDirectory());
if (status != null && !status.isOK()) {
throw new CoreException(status);
}
TcServer.getCallback().publishServer(getTomcatServer(), kind, monitor);
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.publishServerTask, 600);
status = getTomcatConfiguration().cleanupServer(confDir, installDir,
!getTomcatServer().isSaveSeparateContextFiles(), ProgressUtil.getSubMonitorFor(monitor, 100));
if (status != null && !status.isOK()) {
throw new CoreException(status);
}
status = getTomcatConfiguration().backupAndPublish(confDir, !getTomcatServer().isTestEnvironment(),
ProgressUtil.getSubMonitorFor(monitor, 400));
if (status != null && !status.isOK()) {
throw new CoreException(status);
}
status = getTomcatConfiguration().localizeConfiguration(confDir, getServerDeployDirectory(), getTomcatServer(),
ProgressUtil.getSubMonitorFor(monitor, 100));
if (status != null && !status.isOK()) {
throw new CoreException(status);
}
monitor.done();
setServerPublishState(IServer.PUBLISH_STATE_NONE);
}
@Override
public boolean canRestartModule(IModule[] module) {
return true;
}
@Override
public boolean canPublishModule(IModule[] module) {
return true;
}
@Override
public void startModule(IModule[] module, IProgressMonitor monitor) throws CoreException {
int currentState = getServer().getModuleState(module);
if (currentState == IServer.STATE_STOPPED || currentState == IServer.STATE_UNKNOWN) {
monitor.beginTask("Starting Module", 1);
try {
setModuleState(module, IServer.STATE_STARTING);
new StartModuleCommand(this, module).execute();
setModuleState(module, IServer.STATE_STARTED);
monitor.worked(1);
}
catch (TimeoutException e) {
setModuleState(module, IServer.STATE_UNKNOWN);
throw new CoreException(new Status(IStatus.ERROR, TcServerCorePlugin.PLUGIN_ID,
"Cannot start module '" + module[0].getName() + "'", e));
}
}
}
@Override
public void stopModule(IModule[] module, IProgressMonitor monitor) throws CoreException {
int currentState = getServer().getModuleState(module);
if (currentState < IServer.STATE_STOPPING) {
monitor.beginTask("Stopping Modules", 1);
try {
setModuleState(module, IServer.STATE_STOPPING);
new StopModuleCommand(this, module).execute();
setModuleState(module, IServer.STATE_STOPPED);
monitor.worked(1);
}
catch (TimeoutException e) {
setModuleState(module, IServer.STATE_UNKNOWN);
throw new CoreException(new Status(IStatus.ERROR, TcServerCorePlugin.PLUGIN_ID,
"Cannot start module '" + module[0].getName() + "'", e));
}
}
}
@Override
protected void setServerStarted() {
super.setServerStarted();
for (IModule[] module : getAllModules()) {
setModuleState(module, IServer.STATE_STARTED);
}
}
}