[288861] Server view falsely indicates that a republish is needed
diff --git a/plugins/org.eclipse.wst.server.core/schema/publishController.exsd b/plugins/org.eclipse.wst.server.core/schema/publishController.exsd
new file mode 100644
index 0000000..595da6b
--- /dev/null
+++ b/plugins/org.eclipse.wst.server.core/schema/publishController.exsd
@@ -0,0 +1,152 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.wst.server.core" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="org.eclipse.wst.server.core" id="publishController" name="Publish Controller"/>
+      </appInfo>
+      <documentation>
+         This extension point is used to have granual control of the publish action of a server. 
+
+&lt;b&gt;Provisional API:&lt;/b&gt;
+This class/interface is part of an interim API that is still under development and expected to change significantly before reaching stability. It is being made available at this early stage to solicit feedback from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken (repeatedly) as the API evolves.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appInfo>
+            <meta.element />
+         </appInfo>
+      </annotation>
+      <complexType>
+         <sequence>
+            <element ref="publishController" minOccurs="1" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a fully qualified identifier of the target extension point
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  an optional identifier of the extension instance
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  an optional name of the extension instance
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="publishController">
+      <complexType>
+         <attribute name="id" type="string" use="required">
+            <annotation>
+               <documentation>
+                  specifies a unique identifier for this extension point
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="typeIds" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a comma separated list of server type ids that this task may apply to. Used for memory &amp; performance reasons
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a translatable name used to identify this PublishController
+               </documentation>
+               <appInfo>
+                  <meta.attribute translatable="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="description" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a translatable description of the PublishController
+               </documentation>
+               <appInfo>
+                  <meta.attribute translatable="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  specifies the fully qualified name of the Java class that implements &lt;samp&gt;org.eclipse.wst.server.core.model.PublishControllerDelegate&lt;/samp&gt;.
+PublishController instances of this type will delegate to instances of this class.
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="java" basedOn="org.eclipse.wst.server.core.model.PublisherDelegate:"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="order" type="string" use="required">
+            <annotation>
+               <documentation>
+                  an integer that specifies the order that the publisher is processed. if the value is less than 0 it will be processed before calling the server behaviour delegate&apos;s publishing methods, and may modify the workspace. if the value is more than 0, it will be called after the server behaviour delegate&apos;s publishing methods and may not modify the workspace
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiInfo"/>
+      </appInfo>
+      <documentation>
+         Value of the attribute &lt;b&gt;class&lt;/b&gt; must be a fully qualified name of a Java class that implements the interface &lt;code&gt;org.eclipse.wst.server.core.model.PublishController&lt;/code&gt;.
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         3.2
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         The following is an example of a PublishController extension point:
+
+&lt;pre&gt;
+
+&lt;/pre&gt;
+      </documentation>
+   </annotation>
+
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         Copyright (c) 2010 IBM Corporation and others.&lt;br&gt;
+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 
+&lt;a href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/ServerCore.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/ServerCore.java
index 3fb737b..d226f33 100644
--- a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/ServerCore.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/ServerCore.java
@@ -1,5 +1,5 @@
 /**********************************************************************
- * Copyright (c) 2003, 2007 IBM Corporation and others.
+ * Copyright (c) 2003, 2010 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
@@ -13,6 +13,7 @@
 import java.util.*;
 
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.runtime.*;
 import org.eclipse.wst.server.core.internal.*;
 /**
@@ -463,4 +464,15 @@
 	public static boolean isAutoPublishing() {
 		return ServerPreferences.getInstance().isAutoPublishing();
 	}
+
+	public static boolean isPublishRequired(IServer server, IResourceDelta delta2) {
+		PublishController[] controllers = ServerPlugin.getPublishController();
+		if (controllers.length > 0){
+			for (PublishController controller : controllers){
+				if (controller.supportsType(server.getServerType().getId()))
+					return controller.isPublishRequired(server, delta2);
+			}
+		}
+		return true;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/PublishController.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/PublishController.java
new file mode 100644
index 0000000..aa1c124
--- /dev/null
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/PublishController.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.server.core.internal;
+
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.wst.server.core.IServer;
+import org.eclipse.wst.server.core.model.PublishControllerDelegate;
+/**
+ * 
+ */
+public class PublishController {
+	private IConfigurationElement element;
+	private PublishControllerDelegate delegate;
+
+	/**
+	 * Publisher constructor comment.
+	 * 
+	 * @param element a configuration element 
+	 */
+	public PublishController(IConfigurationElement element) {
+		super();
+		this.element = element;
+	}
+
+	/*
+	 * @see
+	 */
+	public String getId() {
+		return element.getAttribute("id");
+	}
+
+	public String getName() {
+		return element.getAttribute("name");
+	}
+
+	public String getDescription() {
+		return element.getAttribute("description");
+	}
+
+	protected String[] getTypeIds() {
+		try {
+			return ServerPlugin.tokenize(element.getAttribute("typeIds"), ",");
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	public boolean supportsType(String id) {
+		return ServerPlugin.contains(getTypeIds(), id);
+	}
+
+	/*
+	 * @see IPublisher#getDelegate()
+	 */
+	public PublishControllerDelegate getDelegate() {
+		if (delegate == null) {
+			try {
+				long time = System.currentTimeMillis();
+				delegate = (PublishControllerDelegate) element.createExecutableExtension("class");
+				Trace.trace(Trace.PERFORMANCE, "PublishTask.getDelegate(): <" + (System.currentTimeMillis() - time) + "> " + getId());
+			} catch (Throwable t) {
+				Trace.trace(Trace.SEVERE, "Could not create delegate" + toString(), t);
+			}
+		}
+		return delegate;
+	}
+
+	public boolean isPublishRequired(IServer server, IResourceDelta delta) {
+		try {
+			Trace.trace(Trace.FINEST, "Task.init " + this);
+			return getDelegate().isPublishRequired(server, delta);
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error calling delegate " + toString(), e);
+			return true;
+		}
+	}
+	
+
+	/**
+	 * Return a string representation of this object.
+	 * 
+	 * @return a string
+	 */
+	public String toString() {
+		return "PublishController[" + getId() + "]";
+	}
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ResourceManager.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ResourceManager.java
index 690efb0..d0c21e4 100644
--- a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ResourceManager.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ResourceManager.java
@@ -99,6 +99,7 @@
 			if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD)
 				return;
 			
+			// search for changes related to Server projects  
 			Trace.trace(Trace.RESOURCES, "->- ServerResourceChangeListener responding to resource change: " + event.getType() + " ->-");
 			IResourceDelta[] children = delta.getAffectedChildren();
 			if (children != null) {
@@ -967,8 +968,7 @@
 		if (project == null)
 			return;
 		
-		if (!deltaContainsChangedFiles(delta))
-			return;
+		IServer[] servers2 = getPublishRequiredServers(delta);
 		
 		// process module changes
 		ProjectModuleFactoryDelegate.handleGlobalProjectChange(project, delta);
@@ -978,8 +978,7 @@
 			return;
 		
 		Trace.trace(Trace.FINEST, "- publishHandleProjectChange");
-		
-		IServer[] servers2 = getServers();
+
 		int size = modules.length;
 		int size2 = servers2.length;
 		for (int i = 0; i < size; i++) {
@@ -991,6 +990,55 @@
 		Trace.trace(Trace.FINEST, "< publishHandleProjectChange");
 	}
 
+	private IServer[] getPublishRequiredServers(IResourceDelta delta){		
+		// The list of servers that will require publish
+		final List<IServer> servers2 = new ArrayList<IServer>();
+
+		// wrksServers = Workspaces Servers
+		final IServer[] wrksServers =  getServers();
+
+		try {
+			delta.accept(new IResourceDeltaVisitor() {
+				public boolean visit(IResourceDelta delta2) throws CoreException {
+					// servers2 is the same size as the list of servers in the workspace, all servers require 
+					// publishing. Exit the visitor
+					if (servers2.size() == wrksServers.length)
+						return false;
+					// has this deltaResource been changed?
+					if (delta2.getKind() == IResourceDelta.NO_CHANGE)
+						return false;
+					
+					if (delta2.getResource() instanceof IFile) {
+						if (delta2.getKind() == IResourceDelta.CHANGED
+							&& (delta2.getFlags() & IResourceDelta.CONTENT) == 0
+							&& (delta2.getFlags() & IResourceDelta.REPLACED) == 0
+							&& (delta2.getFlags() & IResourceDelta.SYNC) == 0){
+							// this resource is effectively a no change
+							return true;
+						}
+						// This is a changed file. 
+						// Iterate through all servers for each changed resource, if the server needs publishing 
+						// for one single resource and the server is not on the list(servers2) then add it, as it 
+						// will require publishing
+						for (IServer server:wrksServers){
+							if (ServerCore.isPublishRequired(server,delta2)){
+								if (!servers2.contains(server))
+									servers2.add(server);
+							}
+						}
+						return false;
+					}
+					// This is a changed folder, so visit the child elements.
+					return true;
+				}
+			});
+		} catch (Exception e) {
+			// ignore
+		}
+		//Trace.trace(Trace.FINEST, "Delta contains change: " + t.b);
+		return servers2.toArray(new IServer[0]);
+	}
+	
 	/**
 	 * Returns <code>true</code> if at least one file in the delta is changed,
 	 * and <code>false</code> otherwise.
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 08696a6..71b35c7 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
@@ -196,6 +196,7 @@
 			final boolean[] changed = new boolean[1];
 			final List<IModule[]> modules2 = new ArrayList<IModule[]>();
 			
+			// create the visitor that will reset the module publish state flag
 			IModuleVisitor visitor = new IModuleVisitor() {
 				public boolean visit(IModule[] module2) {
 					modules2.add(module2);
@@ -215,6 +216,7 @@
 				}
 			};
 			
+			// run the visitor
 			visit(visitor, null);
 			
 			if (getServerPublishInfo().hasStructureChanged(modules2))
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
index 94ce129..f391ff5 100644
--- a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/internal/ServerPlugin.java
@@ -50,6 +50,9 @@
 
 	// cached copy of all publish tasks
 	private static List<IPublishTask> publishTasks;
+	
+	// cached copy of all publish tasks
+	private static List<PublishController> publishControllers;
 
 	// cached copy of all publishers
 	private static List<Publisher> publishers;
@@ -275,6 +278,10 @@
 			}
 		};
 		context.addBundleListener(bundleListener);
+
+		// Load the PublishController during plugin startup since this will be used
+		// during the a workspace delta (changes to the workspace)
+		getPublishController();
 	}
 
 	protected void stopBundle(final String bundleId) {
@@ -732,6 +739,48 @@
 		
 		Trace.trace(Trace.EXTENSION_POINT, "-<- Done loading .publishers extension point -<-");
 	}
+	
+	/**
+	 * Returns an array of all known publishers.
+	 * <p>
+	 * A new array is returned on each call, so clients may store or modify the result.
+	 * </p>
+	 * 
+	 * @return a possibly-empty array of publisher instances {@link Publisher}
+	 */
+	public static PublishController[] getPublishController() {
+		if (publishers == null)
+			loadPublishControllers();
+		PublishController[] controllers = new PublishController[publishControllers.size()];
+		publishControllers.toArray(controllers);
+		return controllers;
+	}
+	
+	/**
+	 * Load the publishController extension point.
+	 */
+	private static synchronized void loadPublishControllers() {
+		if (publishControllers != null)
+			return;
+		Trace.trace(Trace.EXTENSION_POINT, "->- Loading .publishController extension point ->-");
+		IExtensionRegistry registry = Platform.getExtensionRegistry();
+		IConfigurationElement[] cf = registry.getConfigurationElementsFor(ServerPlugin.PLUGIN_ID, "publishController");
+		
+		int size = cf.length;
+		List<PublishController> list = new ArrayList<PublishController>(size);
+		for (int i = 0; i < size; i++) {
+			try {
+				list.add(new PublishController(cf[i]));
+				Trace.trace(Trace.EXTENSION_POINT, "  Loaded .publishController: " + cf[i].getAttribute("id"));
+			} catch (Throwable t) {
+				Trace.trace(Trace.SEVERE, "  Could not load .publishController: " + cf[i].getAttribute("id"), t);
+			}
+		}
+		publishControllers = list;
+		
+		Trace.trace(Trace.EXTENSION_POINT, "-<- Done loading .publishController extension point -<-");
+	}
+	
 
 	/**
 	 * Returns an array of all known module module factories.
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/model/PublishControllerDelegate.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/model/PublishControllerDelegate.java
new file mode 100644
index 0000000..01f46cb
--- /dev/null
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/model/PublishControllerDelegate.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.server.core.model;
+
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.wst.server.core.IServer;
+/**
+ * A controller allows to inteject a different points of the publish action  
+ * <p>
+ * <b>Provisional API:</b> This class/interface is part of an interim API that is still under development and expected to 
+ * change significantly before reaching stability. It is being made available at this early stage to solicit feedback 
+ * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken 
+ * (repeatedly) as the API evolves.
+ * </p>
+ * 
+ * @since 1.1
+ */
+public abstract class PublishControllerDelegate {
+	
+	/**
+	 * Create a new operation. The label and description must be supplied
+	 * by overriding the getLabel() and getDescription() methods.
+	 */
+	public PublishControllerDelegate() {
+		// do nothing
+	}
+
+	public abstract boolean isPublishRequired(IServer server, IResourceDelta delta);
+}
\ No newline at end of file