//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.persistence;

import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.epf.persistence.refresh.RefreshJob;
import org.eclipse.epf.persistence.util.PersistenceResources;
import org.eclipse.epf.uma.CapabilityPattern;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.ContentElement;
import org.eclipse.epf.uma.DeliveryProcess;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.MethodUnit;
import org.eclipse.epf.uma.Process;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.epf.uma.util.IMethodLibraryPersister;
import org.eclipse.epf.uma.util.UmaUtil;
import org.eclipse.osgi.util.NLS;

/**
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class MethodLibraryPersister implements IMethodLibraryPersister {

	private static class FolderInfo {

		EClass eClazz;

		boolean shared;

		private String name;

		/**
		 * @param clazz
		 * @param shared
		 */
		public FolderInfo(EClass clazz, boolean shared) {
			super();
			eClazz = clazz;
			this.shared = shared;
			this.name = lowerAndPluralize(clazz.getName());
		}

	}

	public static final MethodLibraryPersister INSTANCE = new MethodLibraryPersister();

	// need to expose this, jxi
	public static final String RESOURCE_FOLDER = "resources"; //$NON-NLS-1$

	private static final List folderInfos = new ArrayList();

	static {
		folderInfos.add(new FolderInfo(UmaPackage.eINSTANCE.getGuidance(),
				false));
		folderInfos.add(new FolderInfo(UmaPackage.eINSTANCE.getWorkProduct(),
				true));
		folderInfos
				.add(new FolderInfo(UmaPackage.eINSTANCE.getActivity(), true));
	}

	protected List warnings;

	public MethodLibraryPersister() {
	}

	/**
	 * Returns the correct path (relative to the plugin) of the file of the
	 * given content description or null if the given content does not be long
	 * to any plugin.
	 * 
	 * @param content
	 * @return
	 */
	public static String getCorrectPath(ContentDescription content) {
		return getCorrectPath((DescribableElement) content.eContainer(),
				content);
	}

	private static String getCorrectPath(DescribableElement e,
			ContentDescription content) {
		if (e == null)
			return null;
		MethodPlugin plugin = UmaUtil.getMethodPlugin(e);
		if (plugin == null) {
			return null;
		}
		Resource resource = plugin.eResource();
		URI uri;
		if (resource instanceof MultiFileXMIResourceImpl) {
			uri = ((MultiFileXMIResourceImpl) resource).getFinalURI();
		} else {
			uri = resource.getURI();
		}
		File pluginDir = new File(uri.toFileString()).getParentFile();
		String folderPath = staticGetFolderPath(e.eClass());
		String dir = new StringBuffer(pluginDir.getAbsolutePath()).append(
				File.separator).append(folderPath).append(File.separator)
				.toString();

		return getNextAvailableFileName(dir, e, content);
	}

	static String getNextAvailableFileName(String dir, String requestedName,
			MultiResourceEObject e) {
		String currentFilename = null;
		MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) e
				.eDirectResource();
		if (resource != null) {
			// keep existing file name during move if it is not taken yet. Don't
			// try to match the element's name
			//
			String path = ((MultiFileXMIResourceImpl) e.eResource())
					.getFinalURI().toFileString();
			File currentFile = new File(path);
			currentFilename = currentFile.getName();
			File file = new File(dir, currentFilename);
			if (!file.exists()) {
				// the element is being moved
				//
				return file.getAbsolutePath();
			}
		}

		// element without a resource or path is already taken
		//
		String path = new StringBuffer(dir).append(requestedName).append(
				MultiFileSaveUtil.DEFAULT_FILE_EXTENSION).toString();
		if ((currentFilename != null && currentFilename.equals(requestedName))
				|| new File(path).exists()) {
			for (int i = 2; true; i++) {
				path = new StringBuffer(dir).append(requestedName).append(' ')
						.append(i).append(
								MultiFileSaveUtil.DEFAULT_FILE_EXTENSION)
						.toString();
				if (!new File(path).exists()) {
					return path;
				}
			}
		} else {
			return path;
		}
	}

	static String getNextAvailableFileName(String dir,
			ContentDescription content) {
		return getNextAvailableFileName(dir, (DescribableElement) content
				.eContainer(), content);
	}

	private static String getNextAvailableFileName(String dir,
			DescribableElement e, ContentDescription content) {
		return getNextAvailableFileName(dir, e.getName(),
				(MultiResourceEObject) content);
	}

	private static String staticGetFolderPath(EClass eClazz) {
		int size = folderInfos.size();
		FolderInfo info = null;
		for (int i = 0; i < size; i++) {
			FolderInfo folderInfo = (FolderInfo) folderInfos.get(i);
			if (folderInfo.eClazz.isSuperTypeOf(eClazz)) {
				info = folderInfo;
			}
		}
		if (info != null) {
			if (info.eClazz == eClazz || info.shared) {
				return info.name;
			} else {
				return new StringBuffer(info.name).append(File.separatorChar)
						.append(lowerAndPluralize(eClazz.getName())).toString();
			}
		} else {
			return lowerAndPluralize(eClazz.getName());
		}
	}

	public static String lowerAndPluralize(String name) {
		name = name.toLowerCase();
		if (name.endsWith("children")) { //$NON-NLS-1$
			return name;
		} else if (name.endsWith("child")) { //$NON-NLS-1$
			return name + "ren"; //$NON-NLS-1$
		} else if (name.endsWith("data")) { //$NON-NLS-1$
			return name;
		} else if (name.endsWith("datum")) { //$NON-NLS-1$
			return name.substring(0, name.length() - 2) + "a"; //$NON-NLS-1$
		} else if (name.endsWith("by")) { //$NON-NLS-1$
			return name + "s"; //$NON-NLS-1$
		} else if (name.endsWith("y")) { //$NON-NLS-1$
			return name.substring(0, name.length() - 1) + "ies"; //$NON-NLS-1$
		} else if (name.endsWith("ex")) { //$NON-NLS-1$
			return name.substring(0, name.length() - 2) + "ices"; //$NON-NLS-1$
		} else if (name.endsWith("x")) { //$NON-NLS-1$
			return name + "es"; //$NON-NLS-1$
		} else if (name.endsWith("us")) { //$NON-NLS-1$
			return name.substring(0, name.length() - 2) + "i"; //$NON-NLS-1$
		} else if (name.endsWith("ss")) { //$NON-NLS-1$
			return name + "es"; //$NON-NLS-1$
		} else if (name.endsWith("s")) { //$NON-NLS-1$
			return name;
		} else {
			return name + "s"; //$NON-NLS-1$
		}
	}

	/**
	 * Gets the path relative to the library root directory to the resource
	 * folder of the given MethodElement object.
	 * 
	 * @param e
	 * @return
	 */
	public static String getResourcePath(MethodElement e) {
		String folderPath = getElementPath(e);
		if (folderPath == null || folderPath.equals("")) //$NON-NLS-1$
		{
			return RESOURCE_FOLDER;
		} else if (folderPath.endsWith(File.separator)) {
			return new StringBuffer(folderPath).append(RESOURCE_FOLDER)
					.toString();
		} else {
			return new StringBuffer(folderPath).append(File.separator).append(
					RESOURCE_FOLDER).toString();
		}
	}

	public static String getElementPath(MethodElement e) {
		MethodPlugin plugin = UmaUtil.getMethodPlugin(e);
		if (plugin == null) {
			if (!(e instanceof MethodConfiguration || e instanceof MethodLibrary)) {
				// error: plugin element without a valid plugin set. This
				// problem needs to be fixed.
				// still see it from time to time,
				// for example, when lick on a capability pattern in
				// COnfiguration Explorer
				System.out
						.println("error in MethodLibraryPersister.getElementPath(): plugin element without a valid plugin. This problem needs to be fixed."); //$NON-NLS-1$
			}
			return ""; //$NON-NLS-1$
		}

		if (plugin.eContainer() == null) {
			// error: plugin without library set. This problem needs to be
			// fixed.
			// still see it from time to time
			System.out
					.println("error in MethodLibraryPersister.getElementPath(): plugin without library set. This problem needs to be fixed."); //$NON-NLS-1$
			return ""; //$NON-NLS-1$
		}

		String relPluginPath;
		if (plugin.eResource() != null) {
			Resource libRes = UmaUtil.getMethodLibrary(plugin).eResource();
			if (libRes == plugin.eResource()) {
				// the plugin is not saved yet
				relPluginPath = plugin.getName();
			} else {
				URI uri = MultiFileSaveUtil.getFinalURI(plugin.eResource());
				URI relUri = uri.deresolve(MultiFileSaveUtil
						.getFinalURI(libRes));
				relPluginPath = relUri.trimSegments(1).toFileString();
			}
		} else {
			// the library is not saved yet
			relPluginPath = plugin.getName();
		}
		String folderPath = staticGetFolderRelativePath(e);

		return (folderPath.length() == 0 ? relPluginPath : new StringBuffer(
				relPluginPath).append(File.separatorChar).append(folderPath)
				.append(File.separatorChar).toString());
	}

	public static void main(String[] args) {
		EClass eCls = UmaPackage.eINSTANCE.getTemplate();
		System.out.println(eCls.getName());
	}

	/**
	 * Adjusts the location of the given resource and save all the resources
	 * that have been changed as the result of this adjustment
	 * 
	 * @param resource
	 */
	public void adjustLocation(Resource resource) {
		Set modifiedResources = new HashSet();
		MultiFileSaveUtil.adjustLocation(resource, modifiedResources);

		// save the modified resources
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) resource
				.getResourceSet();
		for (Iterator iter = modifiedResources.iterator(); iter.hasNext();) {
			try {
				resourceSet.save((Resource) iter.next(), null);
			} catch (Exception e) {
				CommonPlugin.INSTANCE.log(e);
			}
		}
	}

	public void save(Resource resource) throws Exception {
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) resource
				.getResourceSet();
		resourceSet.save(resource, null);
	}

	protected void deleteFiles(MethodElement e, String path,
			ResourceSet resourceSet) {
		if (new File(path).isDirectory() && !e.eIsProxy()) {
			for (Iterator iter = resourceSet.getResources().iterator(); iter
					.hasNext();) {
				Resource res = (Resource) iter.next();
				MethodElement me = MultiFileSaveUtil.getMethodElement(res);
				if (me != null && UmaUtil.isContainedBy(me, e)) {
					res.unload();
					iter.remove();
				}
			}
		}

		try {
			FileManager.getInstance().deleteResource(path, null);
		} catch (CoreException ex) {
			CommonPlugin.INSTANCE.log(ex);
			throw new WrappedException(ex);
		}
	}

	/**
	 * Deletes the files associated with the given MethodElement
	 * 
	 * @param e
	 */
	protected void delete(MethodElement e, Set modifiedResources) {
		if (!UmaUtil.hasDirectResource(e)) {
			return;
		}

		MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) e
				.eResource();
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) resource
				.getResourceSet();

		// Don't delete this resource's file if it still contains other
		// MethodElement
		for (Iterator iter = resource.getContents().iterator(); iter.hasNext();) {
			Object element = iter.next();
			if (element instanceof MethodElement && element != e) {
				resourceSet.removeURIMappings(e, modifiedResources);
				resource.getContents().remove(e);
				modifiedResources.add(resource);
				return;
			}
		}

		String path;
		if (MultiFileSaveUtil.hasOwnFolder(e)) {
			// path is the directory of the MethodPlugin/ProcessComponent
			//
			path = new File(resource.getFinalURI().toFileString()).getParent();

			// unload/remove all resources under the folder
			//
			Collection unloadedResources = new ArrayList();
			do {
				unloadedResources.clear();
				// collect all resources whose root element is contained by the
				// given element
				//
				for (Iterator iter = new ArrayList(resourceSet.getResources())
						.iterator(); iter.hasNext();) {
					Resource res = (Resource) iter.next();
					if (res.isLoaded()) {
						MethodElement me = MultiFileSaveUtil
								.getMethodElement(res);
						if (me != null && UmaUtil.isContainedBy(me, e)) {
							unloadedResources.add(res);
						}
					}
				}
				for (Iterator iter = unloadedResources.iterator(); iter
						.hasNext();) {
					Resource res = (Resource) iter.next();
					res.unload();
				}
				resourceSet.getResources().removeAll(unloadedResources);
			} while (!unloadedResources.isEmpty());
		} else {
			path = resource.getFinalURI().toFileString();
		}

		try {
			resourceSet.cleanUp(resource, modifiedResources);
		} catch (Exception e1) {
			CommonPlugin.INSTANCE.log(e1);
			if (MultiFileSaveUtil.DEBUG) {
				e1.printStackTrace();
			}
		}

		deleteFiles(e, path, resourceSet);
	}

	/**
	 * @param e
	 * @param objectsWithDirectResources
	 */
	private static void getObjectsWithDirectResources(EObject e,
			Collection objectsWithDirectResources) {
		if (UmaUtil.hasDirectResource(e)) {
			objectsWithDirectResources.add(e);
		} else {
			for (Iterator iter = e.eContents().iterator(); iter.hasNext();) {
				getObjectsWithDirectResources((EObject) iter.next(),
						objectsWithDirectResources);
			}
		}
	}

	/**
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#delete(org.eclipse.epf.uma.MethodElement)
	 */
	public void delete(MethodElement e) {
		// collect all elements with direct resources that are the given element
		// or its offstring
		ArrayList elements = new ArrayList();
		getObjectsWithDirectResources(e, elements);
		if (!elements.isEmpty()) {
			Set modifiedResources = new HashSet();
			for (Iterator iter = elements.iterator(); iter.hasNext();) {
				delete((MethodElement) iter.next(), modifiedResources);
			}

			// save modified resources
			//
			for (Iterator iter = modifiedResources.iterator(); iter.hasNext();) {
				Resource resource = (Resource) iter.next();
				try {
					save(resource);
				} catch (Exception ex) {
					throw new WrappedException(ex);
				}
			}
		}
	}

	/**
	 * Gets the relative path of the folder that can store the content of the
	 * given MethodElement. The path is relative to its plugin folder or library
	 * folder if the element cannot be stored in a plugin.
	 */
	private static String staticGetFolderRelativePath(MethodElement e) {
		if (e instanceof MethodPlugin) {
			return ""; //$NON-NLS-1$
		} else if (e instanceof ContentElement) {
			return staticGetFolderPath(e.eClass());
		} else if (e instanceof MethodConfiguration) {
			return MultiFileSaveUtil.METHOD_CONFIGURATION_FOLDER_NAME;
		}

		MethodUnit unit = UmaUtil.getMethodUnit(e);

		if (unit instanceof ProcessComponent) {
			Process proc = ((ProcessComponent) unit).getProcess();
			if (proc instanceof CapabilityPattern) {
				return MultiFileSaveUtil.CAPABILITY_PATTERN_PATH;
			} else if (proc instanceof DeliveryProcess) {
				return MultiFileSaveUtil.DELIVERY_PROCESS_PATH;
			}
		} else if (unit instanceof ContentDescription) {
			return staticGetFolderRelativePath((MethodElement) unit
					.eContainer());
		}

		return ""; //$NON-NLS-1$
	}

	public String getFolderRelativePath(MethodElement e) {
		return staticGetFolderRelativePath(e);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#getWarnings()
	 */
	public List getWarnings() {
		if (warnings == null) {
			warnings = new ArrayList();
		}
		return warnings;
	}

	static class FailSafePersister extends MethodLibraryPersister implements
			FailSafeMethodLibraryPersister {

		private Map saveOptions;

		private TxRecord txRecord = new TxRecord() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.epf.persistence.TxRecord#clear()
			 */
			public void clear() {
				if (warnings != null && !warnings.isEmpty()) {
					// copy the warning to persister
					//
					FailSafePersister.this.warnings = Collections
							.unmodifiableList(warnings);
				}
				super.clear();
			}
		};

		private Map elementToInfoMapToDeleteFiles;

		public FailSafePersister() {
			saveOptions = new HashMap(
					MultiFileResourceSetImpl.DEFAULT_SAVE_OPTIONS);
			txRecord = new TxRecord();
			saveOptions.put(MultiFileXMISaveImpl.TX_RECORD, txRecord);
			elementToInfoMapToDeleteFiles = new HashMap();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.epf.persistence.MethodLibraryPersister#getWarnings()
		 */
		public List getWarnings() {
			if (txRecord.getTxID() == null) {
				return super.getWarnings();
			}
			return txRecord.getWarnings();
		}

		Map getSaveOptions() {
			return saveOptions;
		}

		void checkMove(Resource resource) {
			// disallow new operation on resource which has started operation
			// that is not committed
			MultiFileXMIResourceImpl mfResource = (MultiFileXMIResourceImpl) resource;
			if (mfResource.txStarted()) {
				throw new MultiFileIOException(NLS.bind(
						PersistenceResources.moveResourceError_msg, resource));
			}
		}

		/**
		 * @see org.eclipse.epf.uma.persistence.MethodLibraryPersister#save(org.eclipse.emf.ecore.resource.Resource)
		 */
		public void save(Resource resource) throws Exception {
			MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) resource
					.getResourceSet();
			if (resourceSet == null) {
				return;
			}

			if (MultiFileXMISaveImpl.checkModifyRequired(saveOptions)) {
				MultiFileSaveUtil.checkModify(resource);
			}

			resourceSet.save(resource, saveOptions);
		}

		/**
		 * @see org.eclipse.epf.uma.persistence.MethodLibraryPersister#adjustLocation(org.eclipse.emf.ecore.resource.Resource)
		 */
		public void adjustLocation(Resource resource) {
			checkMove(resource);
			Set modifiedResources = new HashSet();
			if (MultiFileSaveUtil.prepareAdjustLocation(
					(MultiFileXMIResourceImpl) resource, modifiedResources)) {
				txRecord.getResourcesToCommit().add(resource);

				if (!modifiedResources.isEmpty()) {
					MultiFileSaveUtil.checkModify(modifiedResources);
					MultiFileSaveUtil.checkOutOfSynch(modifiedResources);

					// save the modified resources
					for (Iterator iter = modifiedResources.iterator(); iter
							.hasNext();) {
						try {
							save((Resource) iter.next());
						} catch (Exception e) {
							CommonPlugin.INSTANCE.log(e);
							throw new MultiFileIOException(e.toString());
						}
					}
				}
			}
			commit();
		}

		public void adjustLocation(Collection resources) {
			if (resources == null || resources.isEmpty()) {
				return;
			}

			for (Iterator iter = resources.iterator(); iter.hasNext();) {
				checkMove((Resource) iter.next());
			}

			Set modifiedResources = new HashSet();
			for (Iterator iter = resources.iterator(); iter.hasNext();) {
				MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) iter
						.next();
				if (MultiFileSaveUtil.prepareAdjustLocation(
						(MultiFileXMIResourceImpl) resource, modifiedResources)) {
					txRecord.getResourcesToCommit().add(resource);
				}
			}

			if (!modifiedResources.isEmpty()) {
				MultiFileSaveUtil.checkModify(modifiedResources);
				MultiFileSaveUtil.checkOutOfSynch(modifiedResources);

				// save the modified resources
				for (Iterator iter = modifiedResources.iterator(); iter
						.hasNext();) {
					try {
						save((Resource) iter.next());
					} catch (Exception e) {
						CommonPlugin.INSTANCE.log(e);
						throw new MultiFileIOException(e.toString());
					}
				}
			}
			commit();
		}

		/**
		 * @see org.eclipse.epf.uma.persistence.MethodLibraryPersister#deleteFiles(org.eclipse.epf.uma.MethodElement,
		 *      java.lang.String, org.eclipse.emf.ecore.resource.ResourceSet)
		 */
		protected void deleteFiles(MethodElement e, String path,
				ResourceSet resourceSet) {
			// keep the info to really delete the files in commit()
			elementToInfoMapToDeleteFiles.put(e, new Object[] { path,
					resourceSet });
		}

		private void superDeleteFiles(MethodElement e, String path,
				ResourceSet resourceSet) {
			super.deleteFiles(e, path, resourceSet);
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#commit()
		 */
		public void commit() {
			// save is done
			// call saveIsDone() on every saved file to rename it to the correct
			// name
			int size = txRecord.getResourcesToCommit().size();
			for (int i = 0; i < size; i++) {
				((MultiFileXMIResourceImpl) txRecord.getResourcesToCommit()
						.get(i)).commit();
			}

			// notify all commited resources that the transaction is done
			for (int i = 0; i < size; i++) {
				((MultiFileXMIResourceImpl) txRecord.getResourcesToCommit()
						.get(i)).txFinished(true);
			}

			// delete backup
			for (int i = 0; i < size; i++) {
				((MultiFileXMIResourceImpl) txRecord.getResourcesToCommit()
						.get(i)).deleteBackup();
			}

			txRecord.clear();

			// delete files of deleted elements
			//
			for (Iterator iter = elementToInfoMapToDeleteFiles.entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				Object[] info = (Object[]) entry.getValue();
				String path = (String) info[0];
				try {
					superDeleteFiles((MethodElement) entry.getKey(), path,
							(ResourceSet) info[1]);
				} catch (Exception e) {
					if (e instanceof WrappedException) {
						e = ((WrappedException) e).exception();
					}
					String msg = PersistenceResources.ErrMsg_CouldNotDelete;
					String otherMsg = null;
					if (e instanceof CoreException) {
						IStatus status = ((CoreException) e).getStatus();
						if (status != null) {
							otherMsg = UmaUtil.getMessage(status);
						}
					}
					if (otherMsg == null) {
						otherMsg = ""; //$NON-NLS-1$
					}
					msg = MessageFormat.format(msg, new Object[] { path,
							otherMsg });
					e = new Exception(msg, e);
					getWarnings().add(e);
					CommonPlugin.INSTANCE.log(e);
					if (MultiFileSaveUtil.DEBUG) {
						e.printStackTrace();
					}
				}
			}
			elementToInfoMapToDeleteFiles.clear();
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#rollback()
		 */
		public void rollback() {
			if (!txRecord.getResourcesToCommit().isEmpty()) {
				try {
					int max = txRecord.getResourcesToCommit().size() - 1;
					ArrayList restoredResources = new ArrayList();

					// Something went wrong, restore from backup
					for (int i = max; i > -1; i--) {
						MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) txRecord
								.getResourcesToCommit().get(i);
						if (resource.restore()) {
							restoredResources.add(resource);
						}
					}

					for (Iterator iter = restoredResources.iterator(); iter
							.hasNext();) {
						MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) iter
								.next();
						resource.setModified(true);
					}

					// delete temp files
					for (int i = max; i > -1; i--) {
						MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) txRecord
								.getResourcesToCommit().get(i);
						if (resource.hasTempURI()) {
							// uri keeps the path to temp file
							try {
								new File(resource.getURI().toFileString())
										.delete();
							} catch (Exception e) {
								CommonPlugin.INSTANCE.log(e);
								if (MultiFileSaveUtil.DEBUG) {
									e.printStackTrace();
								}
							}
						}
					}

					// notify all commited resources that the transaction is
					// done
					for (int i = max; i > -1; i--) {
						((MultiFileXMIResourceImpl) txRecord
								.getResourcesToCommit().get(i))
								.txFinished(false);
					}

				} catch (RuntimeException e) {
					CommonPlugin.INSTANCE.log(e);
					if (MultiFileSaveUtil.DEBUG) {
						e.printStackTrace();
					}
					throw e;
				}
			}
			txRecord.clear();
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#getFailSafePersister()
		 */
		public FailSafeMethodLibraryPersister getFailSafePersister() {
			return this;
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#getCurrentTxID()
		 */
		public String getCurrentTxID() {
			return txRecord.getTxID();
		}

	}

	FailSafePersister getFailSafePersister(Map options) {
		FailSafePersister persister = new FailSafePersister();
		persister.saveOptions.putAll(options);

		// make sure that TX_RECORD still keeps the correct value
		persister.saveOptions.put(MultiFileXMISaveImpl.TX_RECORD,
				persister.txRecord);

		return persister;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#hasOwnFolder(java.lang.Object)
	 */
	public boolean hasOwnFolder(Object e) {
		return MultiFileSaveUtil.hasOwnFolder(e);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#hasOwnResource(java.lang.Object)
	 */
	public boolean hasOwnResource(Object e) {
		return MultiFileSaveUtil.hasOwnResource(e,
				MultiFileResourceSetImpl.DEFAULT_SAVE_SEPARATELY_CLASS_SET);
	}

	/**
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#getFailSafePersister()
	 */
	public FailSafeMethodLibraryPersister getFailSafePersister() {
		return new FailSafePersister();
	}

	public static class NonFailSafePersister extends MethodLibraryPersister
			implements FailSafeMethodLibraryPersister {

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#commit()
		 */
		public void commit() {
			// do nothing
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#rollback()
		 */
		public void rollback() {
			// do nothing
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#getCurrentTxID()
		 */
		public String getCurrentTxID() {
			return null;
		}

		/**
		 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister#adjustLocation(java.util.Collection)
		 */
		public void adjustLocation(Collection resources) {
			for (Iterator iter = resources.iterator(); iter.hasNext();) {
				adjustLocation((Resource) iter.next());
			}
		}

	}

	private static final FailSafeMethodLibraryPersister nonFailSafePersister = new NonFailSafePersister();

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#getFileExtension(java.lang.Object)
	 */
	public String getFileExtension(Object e) {
		return MultiFileSaveUtil.DEFAULT_FILE_EXTENSION;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#save(org.eclipse.epf.uma.MethodElement)
	 */
	public void save(MethodElement element) throws Exception {
		if(!hasOwnResourceWithoutReferrer(element)) {
			return;
		}
		MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) UmaUtil.getMethodLibrary(element).eResource().getResourceSet();
		URI uri = MultiFileSaveUtil.createFileURI(element);
		Map options = resourceSet.getDefaultSaveOptions(); 
		MultiFileXMIResourceImpl resource = MultiFileSaveUtil.save(resourceSet, element, uri, options, false);
		resource.updateTimeStamps();
		
		String str = (String) options.get(MultiFileXMISaveImpl.REFRESH_NEW_RESOURCE);
		if (str != null && Boolean.valueOf(str).booleanValue()) {
			// notify RefreshJob the this resource is saved so it will not be
			// reloaded after refreshing it
			//
			RefreshJob.getInstance().resourceSaved(resource);

			// refresh the newly created resource so it is in synch with the
			// workspace
			//
			FileManager.getInstance().refresh(resource);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.epf.uma.util.IMethodLibraryPersister#hasOwnResourceWithoutReferrer(java.lang.Object)
	 */
	public boolean hasOwnResourceWithoutReferrer(Object e) {
		return e instanceof MethodConfiguration;
	}
}