[465141] start job executed twice during 'restart' action
diff --git a/plugins/org.eclipse.wst.server.core/META-INF/MANIFEST.MF b/plugins/org.eclipse.wst.server.core/META-INF/MANIFEST.MF
index 3f34e6a..ec9c22b 100644
--- a/plugins/org.eclipse.wst.server.core/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.wst.server.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.wst.server.core; singleton:=true
-Bundle-Version: 1.6.100.qualifier
+Bundle-Version: 1.6.101.qualifier
 Bundle-Activator: org.eclipse.wst.server.core.internal.ServerPlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/Server.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/Server.java
index 1a6673b..9dd2a33 100644
--- a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/Server.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/Server.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
  * 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
@@ -1901,180 +1901,230 @@
 		StopJob job = new StopJob(force);
 		job.schedule();
 	}
+	
+	/**
+	 * Publish before starting the server.
+	 * 
+	 * If the server is in a state where it must publish before starting, this method
+	 * will run the publishing operation. Otherwise, it will return an OK status. 
+	 *  
+	 * If this is a synchronous call, it will *only* run the publish job, and join on it, 
+	 * returning the result from the job. 
+	 * 
+	 * If this is an asynchronous call, it will schedule a job chain consisting of
+	 * the publish job and the start job, and schedule it. It will then return an OK status. 
+	 * 
+	 * This method should not be used when an IOperationListener is required
+	 * 
+	 * Callers should take care that in the event of an asynchronous call, they are not 
+	 * scheduling the StartJob twice. 
+	 * 
+	 * @param monitor
+	 * @param startJob
+	 * @param synchronous
+	 * @return the status of the publish. Status.OK_STATUS or Status.CANCEL_STATUS if publishing cancelled  
+	 */
 
 	protected IStatus publishBeforeStart(IProgressMonitor monitor, final StartJob startJob, final boolean synchronous){
+		return publishBeforeStart(monitor, startJob, synchronous, null);
+	}
+	
+	protected IStatus publishBeforeStart(IProgressMonitor monitor, final StartJob startJob, final boolean synchronous, final IOperationListener listener){
 
-		// check if we need to publish
-		byte pub = StartJob.PUBLISH_NONE;
-		if (ServerCore.isAutoPublishing() && shouldPublish()) {
-			if (((ServerType)getServerType()).startBeforePublish())
-				return Status.OK_STATUS;
-			pub = StartJob.PUBLISH_BEFORE;
+		if( synchronous) {
+			return publishBeforeStartSynchronous(monitor, listener);
+		}
+		
+		Job j = getPublishAndStartAsynchChainedJob(monitor, startJob, listener);
+		if( j != null ) 
+			j.schedule();
+		// Since this is asynchronous, always return OK_STATUS.
+		return Status.OK_STATUS;
+	}
+
+	/**
+	 * Execute the publish-before-start synchronous job. 
+	 * 
+	 * @param monitor
+	 * @return the status of the publish. Status.OK_STATUS or Status.CANCEL_STATUS if publishing cancelled
+	 */
+	protected IStatus publishBeforeStartSynchronous(IProgressMonitor monitor, final IOperationListener listener){
+		if( shouldPublishBeforeOrAfterStart() != StartJob.PUBLISH_BEFORE) {
+			return Status.OK_STATUS;
 		}
 		
 		final IStatus [] pubStatus = new IStatus[]{Status.OK_STATUS};
 		
-		// publish before start
-		byte publish = pub;
-		if (publish == StartJob.PUBLISH_BEFORE) {
-			PublishJob pubJob = new PublishJob(IServer.PUBLISH_INCREMENTAL, null,false, null);
-			pubJob.addJobChangeListener(new JobChangeAdapter(){
-				public void done(IJobChangeEvent event) {
-					IStatus status = event.getResult();
-					if (status != null && status.getSeverity() == IStatus.ERROR) {
-						pubStatus[0] = status;
-						if (Trace.INFO) {
-							Trace.trace(Trace.STRING_INFO,
-									"Skipping server start job schedule since the server publish failed on a publish before start server.");
-						}
-					} else {
-						if (Trace.INFO) {
-							Trace.trace(Trace.STRING_INFO,
-									"Scheduling server start job after successful publish.");
-						}
-						// Schedule the server start job since the publish operation is completed successfully.
-						startJob.schedule();
-						
-						try {
-							if (synchronous)
-								startJob.join();
-						} catch (InterruptedException e) {
-							if (Trace.WARNING) {
-								Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e);
-							}
-						}
+		// publish before start and wait for it to finish
+		PublishJob pubJob = new PublishJob(IServer.PUBLISH_INCREMENTAL, null,false, null);
+		pubJob.addJobChangeListener(new JobChangeAdapter(){
+			public void done(IJobChangeEvent event) {
+				IStatus status = event.getResult();
+				if (status != null && status.getSeverity() == IStatus.ERROR) {
+					pubStatus[0] = status;
+					if (Trace.INFO) {
+						Trace.trace(Trace.STRING_INFO,
+								"Skipping server start job schedule since the server publish failed on a publish before start server."); //$NON-NLS-1$
 					}
-				}
-				
-			});
-			pubJob.schedule();
-			
-			try {
-				if (synchronous)
-					pubJob.join();
-			} catch (InterruptedException ie) {
-				return Status.CANCEL_STATUS;
+					if( listener != null )
+						listener.done(status);
+				} 
 			}
-		} else {
-			// Schedule the server start since a publish is not needed so 
-			// Schedule the server start job since the publish operation is completed successfully.
-			startJob.schedule();
-			
-			try {
-				if (synchronous)
-					startJob.join();
-			} catch (InterruptedException e) {
-				if (Trace.WARNING) {
-					Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e);
-				}
-			}
+		});
+		pubJob.schedule();
+		try {
+			pubJob.join();
+		} catch (InterruptedException ie) {
+			return Status.CANCEL_STATUS;
 		}
 		return pubStatus[0];
 	}
 	
-	protected IStatus publishAfterStart(IProgressMonitor monitor, boolean synchronous, final IOperationListener op){
-		
+	/**
+	 * The publish-before-start asynchronous job will add a job listener to
+	 * run the start job if the publish was a success. It only does this 
+	 * to ensure the jobs are properly chained. Otherwise, it is impossible to 
+	 * ensure the start job (also scheduled asynchronously) would be run after the completion of 
+	 * the publish job.  
+	 * 
+	 * Anyone calling this method should take care to NOT schedule the startJob on their own, 
+	 * as it's already been called in this instance!
+	 * 
+	 * This method only returns the job, but does not schedule it. 
+	 * It may return null if no job needs to be executed
+	 * 
+	 * @param monitor 
+	 * @param startJob The start job we may wish to chain
+	 * @return the asynchronous publishing and starting Job
+	 */
+	protected Job getPublishAndStartAsynchChainedJob(IProgressMonitor monitor, final StartJob startJob, final IOperationListener listener){
+		if( shouldPublishBeforeOrAfterStart() != StartJob.PUBLISH_BEFORE) {
+			return null;
+		}
+
+		// publish before start
+		PublishJob pubJob = new PublishJob(IServer.PUBLISH_INCREMENTAL, null,false, null);
+		pubJob.addJobChangeListener(new JobChangeAdapter(){
+			public void done(IJobChangeEvent event) {
+				IStatus status = event.getResult();
+				if (status == null || status.getSeverity() != IStatus.ERROR) {
+					if (Trace.INFO) {
+						Trace.trace(Trace.STRING_INFO,
+								"Scheduling server start job after successful publish."); //$NON-NLS-1$
+					}
+					// Schedule the server start job since the publish operation is completed successfully.
+					startJob.schedule();
+				} else {
+					if( listener != null )
+						listener.done(status);
+				}
+			}
+			
+		});
+		return pubJob;
+	}
+	
+	
+	/**
+	 * Get whether we need to publish before or after the start job, 
+	 * or if no publish is required.
+	 *  
+	 * @return
+	 */
+	private byte shouldPublishBeforeOrAfterStart() {
 		// check if we need to publish
 		byte pub = StartJob.PUBLISH_NONE;
 		if (ServerCore.isAutoPublishing() && shouldPublish()) {
 			if (((ServerType)getServerType()).startBeforePublish())
 				pub = StartJob.PUBLISH_AFTER;
 			else {
-				if(op != null) {
-					op.done(Status.OK_STATUS);
-				}
-				return Status.OK_STATUS;
+				pub = StartJob.PUBLISH_BEFORE;
 			}
 		}
+		return pub;
+	}
+	
+	protected IStatus publishAfterStart(IProgressMonitor monitor, boolean synchronous, final IOperationListener op){
+		
+		// check if we need to publish
+		if( shouldPublishBeforeOrAfterStart() != StartJob.PUBLISH_AFTER) {
+			if(op != null) {
+				op.done(Status.OK_STATUS);
+			}
+			return Status.OK_STATUS;
+		}
 		
 		final IStatus [] pubStatus = new IStatus[]{Status.OK_STATUS};
 		
-		// publish after start
-		byte publish = pub;
-		if (publish == StartJob.PUBLISH_AFTER) {
-			PublishJob pubJob = new PublishJob(IServer.PUBLISH_INCREMENTAL, null,false, null);
-			pubJob.addJobChangeListener(new JobChangeAdapter(){
-				public void done(IJobChangeEvent event) {
-					IStatus status = event.getResult();
-					if (status != null && status.getSeverity() == IStatus.ERROR)
-						pubStatus[0] = status; 
-					if (op != null){
-						op.done(status);
-					}
+		PublishJob pubJob = new PublishJob(IServer.PUBLISH_INCREMENTAL, null,false, null);
+		pubJob.addJobChangeListener(new JobChangeAdapter(){
+			public void done(IJobChangeEvent event) {
+				IStatus status = event.getResult();
+				if (status != null && status.getSeverity() == IStatus.ERROR)
+					pubStatus[0] = status; 
+				if (op != null){
+					op.done(status);
 				}
-				
-			});
-			
-			pubJob.schedule();
-			
-			try{
-				if (synchronous)
-					pubJob.join();
 			}
-			catch (InterruptedException ie){
-				return Status.CANCEL_STATUS;
-			}
+			
+		});
+		
+		pubJob.schedule();
+		
+		try{
+			if (synchronous)
+				pubJob.join();
+		}
+		catch (InterruptedException ie){
+			return Status.CANCEL_STATUS;
 		}
 		return pubStatus[0];
 	}
 	
 	/**
+	 * Run the start logic with the synchronization flag taken from the server type
+	 * 
+	 * @param mode2       A mode to run
+	 * @param monitor     A progress monitor
 	 * @see IServer#start(String, IProgressMonitor)
 	 */
 	public void start(String mode2, IProgressMonitor monitor) throws CoreException {
-		if (Trace.FINEST) {
-			Trace.trace(Trace.STRING_FINEST, "Starting server: " + toString() + ", launchMode: " + mode2);
-		}
-		if (getServerType() == null)
-			return;
-		
-		// make sure that the delegate is loaded and the server state is correct
-		loadAdapter(ServerBehaviourDelegate.class, null);
-		
-		StartJob startJob = new StartJob(mode2);
-		// 287442 - only do publish after start if the server start is successful.
-		final IProgressMonitor monitor2 = monitor; 
-		final boolean synchronous = ((ServerType)getServerType()).synchronousStart();
-		startJob.addJobChangeListener(new JobChangeAdapter() {
-			public void done(IJobChangeEvent event) {
-				IStatus resultStatus = event.getResult();
-				if (resultStatus != null && resultStatus.getSeverity() == IStatus.ERROR) { 
-					if (Trace.INFO) {
-						Trace.trace(Trace.STRING_INFO,
-								"Skipping auto publish after server start since the server start failed.");
-					}
-				} else {
-					publishAfterStart(monitor2,synchronous,null);
-				}
-			}
-		});
-		// Forces synchronous start since this method is supposed to wait until the publish operation is completed.
-		IStatus status = publishBeforeStart(monitor, startJob, true);
-		
-		if (status != null && status.getSeverity() == IStatus.ERROR){
-			if (Trace.FINEST) {
-				Trace.trace(Trace.STRING_FINEST, "Failed publish job during start routine");
-			}
-			return;
-		}
-		
-		if (((ServerType)getServerType()).startBeforePublish()) {
-			startJob.schedule();
-			try {
-				if(synchronous)
-					startJob.join();
-			} catch (InterruptedException e) {
-				if (Trace.WARNING) {
-					Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e);
-				}
-			}
-		}
+		start(mode2,  ((ServerType)getServerType()).synchronousStart(), monitor);
+	}
+	
+
+	/**
+	 * Run the start logic, forcing a synchronized workflow
+	 * 
+	 * @param mode2       A mode to run
+	 * @param monitor     A progress monitor
+	 */
+	public void synchronousStart(String mode2, IProgressMonitor monitor) throws CoreException {
+		start(mode2, true, monitor);
 	}
 	
 	/**
-	 * @see IServer#start(String, IOperationListener)
+	 * Run the start logic
+	 * @param mode2       A mode to run
+	 * @param synchronous Whether to do it synchronously or not
+	 * @param monitor     A progress monitor
 	 */
-	public void start(String mode2, final IOperationListener opListener) {
+	private void start(String mode2, final boolean synchronous, IProgressMonitor monitor) {
+		start(mode2, synchronous, null, monitor);
+	}
+	
+	/**
+	 * Run the start logic
+	 * @param mode2       A mode to run
+	 * @param synchronous Whether to do it synchronously or not
+	 * @param opListener  An optional opListener for asynchronous calls
+	 * @param monitor     A progress monitor
+	 */
+	private void start(String mode2, final boolean synchronous, final IOperationListener opListener, IProgressMonitor monitor) {
+		if (Trace.FINEST) {
+			Trace.trace(Trace.STRING_FINEST, "Starting server: " + toString() + ", launchMode: " + mode2); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		// First check some pre-reqs
 		if (getServerType() == null) {
 			if (opListener != null)
 				opListener.done(Status.OK_STATUS);
@@ -2084,119 +2134,81 @@
 		// make sure that the delegate is loaded and the server state is correct
 		loadAdapter(ServerBehaviourDelegate.class, null);
 		
-		// check the publish flag (again) to determine when to call opListener.done 
-		byte pub = StartJob.PUBLISH_NONE;
-		if (ServerCore.isAutoPublishing() && shouldPublish()) {
-			if (((ServerType)getServerType()).startBeforePublish())
-				pub = StartJob.PUBLISH_AFTER;
-			else
-				pub = StartJob.PUBLISH_BEFORE;
-		}
-		
+		// Save all editors
 		if (ServerPlugin.isRunningGUIMode()){
 			ServerPlugin.getSaveEditorHelper().saveAllEditors();
 		}
 		
 		StartJob startJob = new StartJob(mode2);
-		if (opListener != null && pub != StartJob.PUBLISH_AFTER) {
-			startJob.addJobChangeListener(new JobChangeAdapter() {
-				public void done(IJobChangeEvent event) {
-					opListener.done(event.getResult());
-				}
-			});
-		}
-		
-		final boolean synchronous = ((ServerType)getServerType()).synchronousStart();
-		// For publish before start servers, the start job will only be kicked off if the publishing
-		// operation is completed successfully.
-		IStatus status = publishBeforeStart(null, startJob, synchronous);
-		
-		if (status != null && status.getSeverity() == IStatus.ERROR){
-			if (Trace.FINEST) {
-				Trace.trace(Trace.STRING_FINEST, "Failed publish job during start routine");
-			}
-			if (opListener != null)
-				opListener.done(Status.OK_STATUS);
-			return;
-		}
-
-		// 287442 - only do publish after start if the server start is successful.
-		if (pub == StartJob.PUBLISH_AFTER) {
-			startJob.addJobChangeListener(new JobChangeAdapter() {
-				public void done(IJobChangeEvent event) {
+		final IProgressMonitor monitor2 = (monitor == null ? new NullProgressMonitor() : monitor); 
+		final byte pub = shouldPublishBeforeOrAfterStart();
+				
+		// Add listeners for what to do once the startJob is over. 
+		// This may include running a publish (if we publish after start), or
+		// simply marking the opListener as done.  
+		startJob.addJobChangeListener(new JobChangeAdapter() {
+			public void done(IJobChangeEvent event) {
+				if( pub == StartJob.PUBLISH_AFTER ) {
 					IStatus resultStatus = event.getResult();
-					if (resultStatus != null && (resultStatus.getSeverity() == IStatus.ERROR || resultStatus.getSeverity() == IStatus.CANCEL)) { 
-						// Do not launch the publish.
+					// 287442 - only do publish after start if the server start is successful.
+					if (resultStatus != null && resultStatus.getSeverity() == IStatus.ERROR) { 
 						if (Trace.INFO) {
 							Trace.trace(Trace.STRING_INFO,
-									"Skipping auto publish after server start since the server start failed or cancelled.");
+									"Skipping auto publish after server start since the server start failed."); //$NON-NLS-1$
+							if (opListener != null)
+								opListener.done(Status.OK_STATUS);
 						}
-						if (opListener != null)
-							opListener.done(Status.OK_STATUS);
+					} else {
+						publishAfterStart(monitor2,synchronous,opListener);
 					}
-					else {
-						publishAfterStart(null,synchronous,opListener);
-					}
+				} else if( opListener != null ) {
+					// we're marked to publish BEFORE start, so no action needs to be taken after start
+					opListener.done(event.getResult());
 				}
-			});
-			startJob.schedule();
+			}
+		});
+		
+		// Should *we* kick the start job? Or has it been done for us already by a chained asynch call?
+		boolean kickStartJob = true;  
+		if (pub == StartJob.PUBLISH_BEFORE) {
+			// Only one situation can chain the start job for us:  publish_before + asynchronous. 
+			// If our publishBeforeStart is asynchronous, then we must NOT kick the start job ourselves,
+			// because our publishBeforeStart has already chained them together.
+			if( !synchronous )
+				kickStartJob = false;
 			
+			IStatus status = publishBeforeStart(new NullProgressMonitor(), startJob, synchronous, opListener);
+			if (status != null && status.getSeverity() == IStatus.ERROR){
+				if (Trace.FINEST) {
+					Trace.trace(Trace.STRING_FINEST, "Failed publish job during start routine"); //$NON-NLS-1$
+				}
+				if (opListener != null)
+					opListener.done(Status.OK_STATUS);
+				return;
+			}
+		}
+
+		// If the asynchronous publish job is kicking the start job, then we don't kick it. 
+		if( kickStartJob ) {
+			startJob.schedule();
 			try {
 				if(synchronous)
 					startJob.join();
 			} catch (InterruptedException e) {
 				if (Trace.WARNING) {
-					Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e);
+					Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e); //$NON-NLS-1$
 				}
 			}
 		}
 	}
-
-	public void synchronousStart(String mode2, IProgressMonitor monitor) throws CoreException {
-		if (getServerType() == null)
-			return;
-		
-		// make sure that the delegate is loaded and the server state is correct
-		loadAdapter(ServerBehaviourDelegate.class, monitor);
-		
-		StartJob startJob = new StartJob(mode2);
-		// 287442 - only do publish after start if the server start is successful.
-		final IProgressMonitor monitor2 = monitor; 
-		startJob.addJobChangeListener(new JobChangeAdapter() {
-			public void done(IJobChangeEvent event) {
-				IStatus resultStatus = event.getResult();
-				if (resultStatus != null && resultStatus.getSeverity() == IStatus.ERROR) { 
-					if (Trace.INFO) {
-						Trace.trace(Trace.STRING_INFO,
-								"Skipping auto publish after server start since the server start failed.");
-					}
-				} else {
-					publishAfterStart(monitor2,true,null);
-				}
-			}
-		});
-		
-		IStatus status = publishBeforeStart(monitor, startJob, true);
-		
-		if (status != null && status.getSeverity() == IStatus.ERROR){
-			if (Trace.FINEST) {
-				Trace.trace(Trace.STRING_FINEST, "Failed publish job during start routine");
-			}
-			return;
-		}
-		
-		if (((ServerType)getServerType()).startBeforePublish()) {
-			startJob.schedule();
-			
-			try {
-				startJob.join();
-			} catch (InterruptedException e) {
-				if (Trace.WARNING) {
-					Trace.trace(Trace.STRING_WARNING, "Error waiting for job", e);
-				}
-			}
-		}
+	
+	/**
+	 * @see IServer#start(String, IOperationListener)
+	 */
+	public void start(String mode2, final IOperationListener opListener) {
+		start(mode2, ((ServerType)getServerType()).synchronousStart(), opListener, new NullProgressMonitor());
 	}
+	
 
 	/*
 	 * @see IServer#synchronousRestart(String, IProgressMonitor)