155 patches
diff --git a/features/org.eclipse.wst.server_core.feature.patch/buildnotes_org.eclipse.wst.server_core.feature.patch.html b/features/org.eclipse.wst.server_core.feature.patch/buildnotes_org.eclipse.wst.server_core.feature.patch.html
index a7430bc..d2950f8 100644
--- a/features/org.eclipse.wst.server_core.feature.patch/buildnotes_org.eclipse.wst.server_core.feature.patch.html
+++ b/features/org.eclipse.wst.server_core.feature.patch/buildnotes_org.eclipse.wst.server_core.feature.patch.html
@@ -19,4 +19,5 @@
 <p>Bug <a href='https://bugs.eclipse.org/bugs/show_bug.cgi?id=211073'>211073</a>. Excessive number of output in the .log file as a result of the fix to bug 204165</p>
 <p>Bug <a href='https://bugs.eclipse.org/bugs/show_bug.cgi?id=210702'>210702</a>. Closing the Servers project should refresh the Servers view</p>
 <p>Bug <a href='https://bugs.eclipse.org/bugs/show_bug.cgi?id=218636'>218636</a>. Performance problem remains, in Server -> Add/ Remove Projects</p>
+<p>Bug <a href='https://bugs.eclipse.org/bugs/show_bug.cgi?id=274644'>274644</a>. JARs deployed wrongly within EAR</p>
 </body></html>
\ No newline at end of file
diff --git a/features/org.eclipse.wst.server_core.feature.patch/feature.properties b/features/org.eclipse.wst.server_core.feature.patch/feature.properties
index 2b8f0aa..c2cb270 100644
--- a/features/org.eclipse.wst.server_core.feature.patch/feature.properties
+++ b/features/org.eclipse.wst.server_core.feature.patch/feature.properties
@@ -36,6 +36,7 @@
 Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=211073 Excessive number of output in the .log file\n\
 Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=210702 Closing the Servers project should refresh the Servers view\n\
 Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=218636 Performance problem remains, in Server -> Add/ Remove Projects\n\
+Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=274644 JARs deployed wrongly within EAR\n\
 \n\
 # "copyright" property - text of the "Feature Update Copyright"
 copyright=\
diff --git a/plugins/org.eclipse.jst.server.generic.core/src/org/eclipse/jst/server/generic/core/internal/publishers/EarModuleAssembler.java b/plugins/org.eclipse.jst.server.generic.core/src/org/eclipse/jst/server/generic/core/internal/publishers/EarModuleAssembler.java
index e75cd7c..327e89b 100644
--- a/plugins/org.eclipse.jst.server.generic.core/src/org/eclipse/jst/server/generic/core/internal/publishers/EarModuleAssembler.java
+++ b/plugins/org.eclipse.jst.server.generic.core/src/org/eclipse/jst/server/generic/core/internal/publishers/EarModuleAssembler.java
@@ -1,5 +1,5 @@
 /***************************************************************************************************
- * Copyright (c) 2005 Eteration A.S. and Gorkem Ercan. All rights reserved. This program and the
+ * Copyright (c) 2005, 2009 Eteration A.S. and Gorkem Ercan. 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
@@ -18,9 +18,15 @@
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jst.server.core.IEnterpriseApplication;
+import org.eclipse.jst.server.core.IJ2EEModule;
+import org.eclipse.jst.server.core.PublishUtil;
 import org.eclipse.jst.server.generic.core.internal.CorePlugin;
 import org.eclipse.jst.server.generic.core.internal.GenericServer;
 import org.eclipse.wst.server.core.IModule;
+import org.eclipse.wst.server.core.internal.Server;
+import org.eclipse.wst.server.core.model.IModuleResource;
+import org.eclipse.wst.server.core.model.IModuleResourceDelta;
+import org.eclipse.wst.server.core.util.ProjectModule;
 
 /**
  * Utility for EAR module assembly.
@@ -33,23 +39,43 @@
 	}
 
 	public IPath assemble(IProgressMonitor monitor) throws CoreException{
+		//copy ear root to the temporary assembly directory
 		IPath parent =copyModule(fModule,monitor);
 		IEnterpriseApplication earModule = (IEnterpriseApplication)fModule.loadAdapter(IEnterpriseApplication.class, monitor);
 		IModule[] childModules = earModule.getModules();
 		for (int i = 0; i < childModules.length; i++) {
 			IModule module = childModules[i];
 			String uri = earModule.getURI(module);
-			if(uri==null){
+			if(uri==null){ //The bad memories of WTP 1.0
 				IStatus status = new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, 0,	"unable to assemble module null uri",null ); //$NON-NLS-1$
 				throw new CoreException(status);
 			}
-				
-			packModule(module,uri, parent);
+			IJ2EEModule jeeModule = (IJ2EEModule) module.loadAdapter(IJ2EEModule.class,monitor);
+			if( jeeModule != null && jeeModule.isBinary() ){//Binary module just copy
+				ProjectModule pm = (ProjectModule) module.loadAdapter(ProjectModule.class, null);
+				IModuleResource[] resources = pm.members();
+				PublishUtil.publishFull(resources, parent, monitor);
+				continue;//done! no need to go further
+			}
+			if( shouldRepack( module ) ){	
+			    packModule(module,uri, parent);
+			}
 		}
 		return parent;
 	}
-	
-	protected void packModule(IModule module, String deploymentUnitName, IPath destination) throws CoreException {
+	/**
+     * Checks if there has been a change in the published resources.
+     * @param module
+     * @return module changed
+	 */
+	private boolean shouldRepack( IModule module ) {
+        final Server server = (Server) fServer.getServer();
+        final IModule[] modules ={module}; 
+        IModuleResourceDelta[] deltas = server.getPublishedResourceDelta( modules );
+        return deltas.length > 0;
+    }
+
+    protected void packModule(IModule module, String deploymentUnitName, IPath destination) throws CoreException {
 		if(module.getModuleType().getId().equals("jst.web")) //$NON-NLS-1$
 		{
 			AbstractModuleAssembler assembler= AbstractModuleAssembler.Factory.getModuleAssembler(module, fServer);
diff --git a/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/util/PublishHelper.java b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/util/PublishHelper.java
new file mode 100644
index 0000000..c603cfe
--- /dev/null
+++ b/plugins/org.eclipse.wst.server.core/servercore/org/eclipse/wst/server/core/util/PublishHelper.java
@@ -0,0 +1,344 @@
+/*******************************************************************************
+ * Copyright (c) 2008,2009 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.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+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.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.server.core.internal.Messages;
+import org.eclipse.wst.server.core.internal.ProgressUtil;
+import org.eclipse.wst.server.core.internal.ServerPlugin;
+import org.eclipse.wst.server.core.internal.Trace;
+import org.eclipse.wst.server.core.model.IModuleFile;
+import org.eclipse.wst.server.core.model.IModuleFolder;
+import org.eclipse.wst.server.core.model.IModuleResource;
+/**
+ * Utility class with an assortment of useful publishing file methods.
+ *
+ * @since 3.0
+ */
+public final class PublishHelper {
+	// size of the buffer
+	private static final int BUFFER = 65536;
+
+	// the buffer
+	private static byte[] buf = new byte[BUFFER];
+
+	private static final IStatus[] EMPTY_STATUS = new IStatus[0];
+
+	private static final File defaultTempDir = ServerPlugin.getInstance().getStateLocation().toFile();
+
+	private static final String TEMPFILE_PREFIX = "tmp";
+
+	private File tempDir;
+
+	/**
+	 * Create a new PublishHelper.
+	 * 
+	 * @param tempDirectory a temporary directory to use during publishing, or <code>null</code>
+	 *    to use the default. If it does not exist, the folder will be created
+	 */
+	public PublishHelper(File tempDirectory) {
+		this.tempDir = tempDirectory;
+		if (tempDir == null)
+			tempDir = defaultTempDir;
+		else if (!tempDir.exists())
+			tempDir.mkdirs();
+	}
+
+	/**
+	 * Copy a file from a to b. Closes the input stream after use.
+	 * 
+	 * @param in an input stream
+	 * @param to a path to copy to. the directory must already exist
+	 * @param ts timestamp
+	 * @throws CoreException if anything goes wrong
+	 */
+	private void copyFile(InputStream in, IPath to, long ts, IModuleFile mf) throws CoreException {
+		OutputStream out = null;
+		
+		File tempFile = null;
+		try {
+			File file = to.toFile();
+			tempFile = File.createTempFile(TEMPFILE_PREFIX, "." + to.getFileExtension(), tempDir);
+			
+			out = new FileOutputStream(tempFile);
+			
+			int avail = in.read(buf);
+			while (avail > 0) {
+				out.write(buf, 0, avail);
+				avail = in.read(buf);
+			}
+			
+			out.close();
+			out = null;
+			
+			moveTempFile(tempFile, file);
+			
+			if (ts != IResource.NULL_STAMP && ts != 0)
+				file.setLastModified(ts);
+		} catch (CoreException e) {
+			throw e;
+		} catch (Exception e) {
+			IPath path = mf.getModuleRelativePath().append(mf.getName());
+			Trace.trace(Trace.SEVERE, "Error copying file: " + path.toOSString() + " to " + to.toOSString(), e);
+			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorSaving, path.toOSString(), e.getLocalizedMessage()), null));
+		} finally {
+			if (tempFile != null && tempFile.exists())
+				tempFile.deleteOnExit();
+			try {
+				if (in != null)
+					in.close();
+			} catch (Exception ex) {
+				// ignore
+			}
+			try {
+				if (out != null)
+					out.close();
+			} catch (Exception ex) {
+				// ignore
+			}
+		}
+	}
+
+	/**
+	 * Safe delete. Tries to delete multiple times before giving up.
+	 * 
+	 * @param f
+	 * @return <code>true</code> if it succeeds, <code>false</code> otherwise
+	 */
+	private static boolean safeDelete(File f, int retrys) {
+		int count = 0;
+		while (count < retrys) {
+			if (!f.exists())
+				return true;
+			
+			f.delete();
+			
+			if (!f.exists())
+				return true;
+			
+			count++;
+			// delay if we are going to try again
+			if (count < retrys) {
+				try {
+					Thread.sleep(100);
+				} catch (Exception e) {
+					// ignore
+				}
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * Utility method to move a temp file into position by deleting the original and
+	 * swapping in a new copy.
+	 *  
+	 * @param tempFile
+	 * @param file
+	 * @throws CoreException
+	 */
+	private void moveTempFile(File tempFile, File file) throws CoreException {
+		if (file.exists()) {
+			if (!safeDelete(file, 2)) {
+				// attempt to rewrite an existing file with the tempFile contents if
+				// the existing file can't be deleted to permit the move
+				try {
+					InputStream in = new FileInputStream(tempFile);
+					IStatus status = copyFile(in, file.getPath());
+					if (!status.isOK()) {
+						Trace.trace(Trace.SEVERE, "Error moving file: " + file.toString(), status.getException());
+						MultiStatus status2 = new MultiStatus(ServerPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorSaving, file.toString()), null);
+						status2.add(status);
+						throw new CoreException(status2);
+					}
+					return;
+				} catch (FileNotFoundException e) {
+					// shouldn't occur
+				} finally {
+					tempFile.delete();
+				}
+			}
+		}
+		if (!safeRename(tempFile, file, 10)){
+			Trace.trace(Trace.SEVERE, "Error moving temp file: " + tempFile.toString());
+			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorSaving, tempFile.toString()), null));
+		}
+			
+	}
+	
+	/**
+	 * Safe rename. Will try multiple times before giving up.
+	 * 
+	 * @param from
+	 * @param to
+	 * @param retrys number of times to retry
+	 * @return <code>true</code> if it succeeds, <code>false</code> otherwise
+	 */
+	private static boolean safeRename(File from, File to, int retrys) {
+		// make sure parent dir exists
+		File dir = to.getParentFile();
+		if (dir != null && !dir.exists())
+			dir.mkdirs();
+		
+		int count = 0;
+		while (count < retrys) {
+			if (from.renameTo(to))
+				return true;
+			
+			count++;
+			// delay if we are going to try again
+			if (count < retrys) {
+				try {
+					Thread.sleep(100);
+				} catch (Exception e) {
+					// ignore
+				}
+			}
+		}
+		return false;
+	}
+	
+	private void copyFile(IModuleFile mf, IPath path) throws CoreException {
+		Trace.trace(Trace.FINEST, "Copying: " + mf.getName() + " to " + path.toString());
+		
+		IFile file = (IFile) mf.getAdapter(IFile.class);
+		if (file != null)
+			copyFile(file.getContents(), path, file.getLocalTimeStamp(), mf);
+		else {
+			File file2 = (File) mf.getAdapter(File.class);
+			InputStream in = null;
+			try {
+				in = new FileInputStream(file2);
+			} catch (IOException e) {
+				Trace.trace(Trace.SEVERE, "Error copying file: " + file2.getAbsolutePath() , e);
+				throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorSaving, file2.getAbsolutePath()), e));
+			}
+			copyFile(in, path, file2.lastModified(), mf);
+		}
+	}
+
+	/**
+	 * Publish the given module resources to the given path.
+	 * 
+	 * @param resources an array of module resources
+	 * @param path a path to publish to
+	 * @param monitor a progress monitor, or <code>null</code> if progress
+	 *    reporting and cancellation are not desired
+	 * @return a possibly-empty array of error and warning status
+	 */
+	public IStatus[] publishFull(IModuleResource[] resources, IPath path, IProgressMonitor monitor) {
+		if (resources == null)
+			return EMPTY_STATUS;
+		
+		monitor = ProgressUtil.getMonitorFor(monitor);
+		
+		List status = new ArrayList(2);
+		int size = resources.length;
+		for (int i = 0; i < size; i++) {
+			IStatus[] stat = copy(resources[i], path, monitor);
+			addArrayToList(status, stat);
+		}
+		
+		IStatus[] stat = new IStatus[status.size()];
+		status.toArray(stat);
+		return stat;
+	}
+
+	private IStatus[] copy(IModuleResource resource, IPath path, IProgressMonitor monitor) {
+		String name = resource.getName();
+		Trace.trace(Trace.FINEST, "Copying: " + name + " to " + path.toString());
+		List status = new ArrayList(2);
+		if (resource instanceof IModuleFolder) {
+			IModuleFolder folder = (IModuleFolder) resource;
+			IStatus[] stat = publishFull(folder.members(), path, monitor);
+			addArrayToList(status, stat);
+		} else {
+			IModuleFile mf = (IModuleFile) resource;
+			path = path.append(mf.getModuleRelativePath()).append(name);
+			File f = path.toFile().getParentFile();
+			if (!f.exists())
+				f.mkdirs();
+			try {
+				copyFile(mf, path);
+			} catch (CoreException ce) {
+				status.add(ce.getStatus());
+			}
+		}
+		IStatus[] stat = new IStatus[status.size()];
+		status.toArray(stat);
+		return stat;
+	}
+	
+	/**
+	 * Copy a file from a to b. Closes the input stream after use.
+	 *
+	 * @param in an InputStream
+	 * @param to the file to copy to
+	 * @return a status
+	 */
+	private IStatus copyFile(InputStream in, String to) {
+		OutputStream out = null;
+		
+		try {
+			out = new FileOutputStream(to);
+			
+			int avail = in.read(buf);
+			while (avail > 0) {
+				out.write(buf, 0, avail);
+				avail = in.read(buf);
+			}
+			return Status.OK_STATUS;
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error copying file", e);
+			return new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorSaving, new String[] {to, e.getLocalizedMessage()}), e);
+		} finally {
+			try {
+				if (in != null)
+					in.close();
+			} catch (Exception ex) {
+				// ignore
+			}
+			try {
+				if (out != null)
+					out.close();
+			} catch (Exception ex) {
+				// ignore
+			}
+		}
+	}
+
+	private static void addArrayToList(List list, IStatus[] a) {
+		if (list == null || a == null || a.length == 0)
+			return;
+		
+		int size = a.length;
+		for (int i = 0; i < size; i++)
+			list.add(a[i]);
+	}
+}