/******************************************************************************
* Copyright (c) 2009-2013, Linagora
*
* 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:
* Linagora - initial API and implementation
*******************************************************************************/
package com.ebmwebsourcing.petals.server;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.model.ServerBehaviourDelegate;
import org.ow2.petals.kernel.ws.api.PEtALSWebServiceException;
import org.ow2.petals.kernel.ws.client.PetalsClient;
import org.ow2.petals.kernel.ws.client.PetalsClientFactory;
import com.ebmwebsourcing.petals.server.runtime.PetalsRuntime;
import com.ebmwebsourcing.petals.server.server.PetalsServer;
import com.ebmwebsourcing.petals.server.ui.preferences.PetalsServerPreferencePage;
/**
* The server behavior is in charge of maintaining the state of the server.
* <p>
* It handles, among other things, the stop and shutdown command of Petals
* servers. Initially, it listens to the server by checking the system process and by
* pinging the server at regular intervals.
* </p>
*
* @author Vincent Zurczak - EBM WebSourcing
*/
public class PetalsServerBehavior extends ServerBehaviourDelegate {
private IDebugEventSetListener processListener;
private boolean keepOnPinging;
private Thread pingThread;
/**
* @return the Petals runtime
*/
public PetalsRuntime getPetalsRuntime() {
PetalsRuntime petalsRuntime = null;
if( getServer().getRuntime() != null )
petalsRuntime = (PetalsRuntime) getServer().getRuntime().loadAdapter( PetalsRuntime.class, null );
return petalsRuntime;
}
/**
* @return the Petals server
*/
public PetalsServer getPetalsServer() {
PetalsServer server = (PetalsServer) getServer().loadAdapter( PetalsServer.class, null );
return server;
}
/**
* @return the name of the class to use to start the server in a JVM
*/
public String getRuntimeClass() {
return getPetalsServer().getPetalsRuntimeClass();
}
/*
* (non-Javadoc)
* @see org.eclipse.wst.server.core.model.ServerBehaviourDelegate
* #initialize(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected void initialize( IProgressMonitor monitor ) {
super.initialize( monitor );
// Get the server initial state
boolean running = getPetalsServer().isRunning();
if( running )
setServerState( IServer.STATE_STARTED );
else
setServerState( IServer.STATE_STOPPED );
}
/*
* (non-Javadoc)
* @see org.eclipse.wst.server.core.model.ServerBehaviourDelegate#dispose()
*/
@Override
public void dispose() {
stop( false, true );
super.dispose();
}
/**
* Starts listening the system process and pinging the server.
* @param newProcess
*/
public void startListening( final IProcess newProcess ) {
// Listen to the system process
if( this.processListener != null || newProcess == null || this.pingThread != null )
return;
this.processListener = new IDebugEventSetListener() {
@Override
public void handleDebugEvents(DebugEvent[] events) {
if( events != null ) {
int size = events.length;
for( int i=0; i<size; i++ ) {
if( newProcess != null
&& newProcess.equals( events[ i ].getSource())
&& events[ i ].getKind() == DebugEvent.TERMINATE ) {
// Atomic section
synchronized( this ) {
stopListening();
setServerState( IServer.STATE_STOPPED );
}
}
}
}
}
};
DebugPlugin.getDefault().addDebugEventListener( this.processListener );
// Ping the server while it is starting
final Thread startingThread = new Thread() {
@Override
public void run() {
// Ping every second until timeout is over
for( int i=0; i<getServer().getStartTimeout(); i++) {
try {
Thread.sleep( 1000 );
} catch( InterruptedException e ) {
// nothing
}
// Update the state
boolean running = getPetalsServer().isRunning();
// Atomic section
synchronized( this ) {
if( running ) {
setServerState( IServer.STATE_STARTED );
break;
}
}
}
// If the server is still not started, stop listening
// No need to make this part atomic. The other thread is not started.
if( ! getPetalsServer().isRunning()) {
stopListening();
setServerState( IServer.STATE_STOPPED );
}
};
};
startingThread.start();
// Ping the server to check its state
setKeepOnPinging( true );
this.pingThread = new Thread() {
@Override
public void run() {
// Wait the starting thread to finish
try {
startingThread.join();
} catch( InterruptedException e1 ) {
return;
}
// Ping every 10 seconds
while( keepOnPinging()) {
try {
Thread.sleep( 10000);
} catch( InterruptedException e ) {
// nothing
}
// Update the state
boolean running = getPetalsServer().isRunning();
// Atomic section
synchronized( this ) {
if( keepOnPinging()) {
if( running )
setServerState( IServer.STATE_STARTED );
else
setServerState( IServer.STATE_STOPPED );
}
}
}
}
};
this.pingThread.setDaemon( true );
this.pingThread.start();
}
/**
* Stops listening the system process and pinging the server.
*/
private void stopListening () {
if( this.pingThread != null ) {
setKeepOnPinging( false );
this.pingThread = null;
}
if( this.processListener != null ) {
DebugPlugin.getDefault().removeDebugEventListener( this.processListener );
this.processListener = null;
}
}
/**
* Stops the server.
* @param force true to stop the process if the normal stop command fails
* @see org.eclipse.wst.server.core.model.ServerBehaviourDelegate
* #stop(boolean)
*/
@Override
public void stop( boolean force ) {
stop( false, force );
}
/**
* Shutdowns the server.
* <p>
* Shutdown means all the deployed artifacts will be removed.
* </p>
*
* @param eraseAll true to erase all the deployed artifacts before stopping the container
* @param force true to stop the process if the normal shutdown command fails
*/
public void stop( boolean eraseAll, boolean force ) {
int serverState = getServer().getServerState();
if( serverState == IServer.STATE_STOPPED )
return;
// Atomic section
synchronized( this ) {
stopListening();
setServerState( IServer.STATE_STOPPING );
}
// Stop with web services
boolean normalShutdownFailed = false;
String serverAddress = getPetalsServer().getWsUrlAsString();
try {
if( getPetalsServer().isRunning()) {
PetalsClient client = PetalsClientFactory.getInstance().getClient( serverAddress );
if( eraseAll )
client.getRuntimeService().shutdownContainer();
else
client.getRuntimeService().stopContainer();
}
} catch( PEtALSWebServiceException e ) {
normalShutdownFailed = true;
}
// Handle errors and forced stop
if( normalShutdownFailed && force )
terminateProcess();
setServerState( IServer.STATE_STOPPED );
}
/**
* Terminates the launch process.
*/
private void terminateProcess () {
try {
ILaunch launch = getServer().getLaunch();
if( launch != null )
launch.terminate();
} catch( DebugException e ) {
PetalsServerPlugin.log( e, IStatus.ERROR );
}
}
/**
* Prepares the server to be launched, checking the runtime and cleaning the install directory.
*
* @param launch
* @param launchMode
* @param monitor
* @throws CoreException
*/
public void setupLaunch( ILaunch launch, String launchMode, IProgressMonitor monitor )
throws CoreException {
// Clean the server install directory
if( getPetalsServer().isRunning())
setServerState( IServer.STATE_STARTED );
int serverState = getServer().getServerState();
if( serverState == IServer.STATE_STARTED
|| serverState == IServer.STATE_STARTING )
return;
if( getPetalsServer().isServerInstallationDirty()) {
IStatus status = getPetalsServer().cleanServerInstallation();
if( status != null && status.getSeverity() == IStatus.ERROR )
throw new CoreException( status );
}
// Check the runtime (may have been edited)
IStatus status = getPetalsRuntime().validate();
if( status != null && status.getSeverity() == IStatus.ERROR )
throw new CoreException( status );
// Update the server state
setServerRestartState( false );
setServerState( IServer.STATE_STARTING );
setMode( launchMode );
}
/**
* Adds the server libraries in the launch class path.
* @see org.eclipse.wst.server.core.model.ServerBehaviourDelegate
* #setupLaunchConfiguration(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void setupLaunchConfiguration(
ILaunchConfigurationWorkingCopy workingCopy,
IProgressMonitor monitor ) throws CoreException {
// Add the server libraries in the class path
List<String> classpath = new ArrayList<String>();
for( Object o : workingCopy.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_CLASSPATH,
Collections.emptyList())) {
if( o instanceof String )
classpath.add((String) o);
}
List<File> serverLibs = getPetalsServer().getPetalsServerLibraries();
for( File serverLib : serverLibs ) {
Path path = new Path( serverLib.getAbsolutePath());
IRuntimeClasspathEntry entry = JavaRuntime.newArchiveRuntimeClasspathEntry( path );
entry.setClasspathProperty( IRuntimeClasspathEntry.USER_CLASSES );
classpath.add( entry.getMemento());
}
// Update and force the use of this class path in the launch configuration
workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_CLASSPATH, classpath );
workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, false );
// Get the server arguments
String args = null;
final IPreferenceStore store = PetalsServerPlugin.getDefault().getPreferenceStore();
if( store.contains( PetalsServerPreferencePage.START_IN_CONSOLE_MODE )) {
String startMode = store.getString( PetalsServerPreferencePage.START_IN_CONSOLE_MODE );
if( MessageDialogWithToggle.ALWAYS.equals( startMode ))
args = "start -console";
else if( MessageDialogWithToggle.NEVER.equals( startMode ))
args = "start";
}
if( args == null ) {
Display.getDefault().syncExec( new Runnable() {
@Override
public void run() {
MessageDialogWithToggle.openYesNoQuestion(
new Shell(), "Start Mode",
"Do you want to start in console mode?",
"Do not ask again", false, store,
PetalsServerPreferencePage.START_IN_CONSOLE_MODE );
};
});
String startMode = store.getString( PetalsServerPreferencePage.START_IN_CONSOLE_MODE );
if( MessageDialogWithToggle.ALWAYS.equals( startMode ))
args = "start -console";
else
args = "start";
}
workingCopy.setAttribute( IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, args );
}
/**
* Must be called when the server launch fails.
*/
public void signalLaunchFailed() {
setServerState( IServer.STATE_STOPPED );
}
/**
* @return the keepOnPinging
*/
public synchronized boolean keepOnPinging() {
return this.keepOnPinging;
}
/**
* @param keepOnPinging the keepOnPinging to set
*/
public synchronized void setKeepOnPinging( boolean keepOnPinging ) {
this.keepOnPinging = keepOnPinging;
}
}