Index: gs3-extensions/vm-manager/trunk/src/src/java/org/greenstone/gsdl3/action/VMManagerAction.java
===================================================================
--- gs3-extensions/vm-manager/trunk/src/src/java/org/greenstone/gsdl3/action/VMManagerAction.java	(revision 38655)
+++ gs3-extensions/vm-manager/trunk/src/src/java/org/greenstone/gsdl3/action/VMManagerAction.java	(revision 38655)
@@ -0,0 +1,485 @@
+package org.greenstone.gsdl3.action;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Serializable;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.io.FileUtils;
+import org.greenstone.gsdl3.util.DerbyWrapper;
+import org.greenstone.gsdl3.util.GSConstants;
+import org.greenstone.gsdl3.util.GSParams;
+import org.greenstone.gsdl3.util.GSXML;
+import org.greenstone.gsdl3.util.GSXSLT;
+import org.greenstone.gsdl3.util.UserContext;
+import org.greenstone.gsdl3.util.XMLConverter;
+import org.greenstone.util.GlobalProperties;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+public class VMManagerAction extends Action
+{
+	//Sub actions
+	private final String VMM_RETRIEVE_WIZARD = "getwizard";
+	private final String VMM_CREATE_VM = "createvm";
+	private final String VMM_CLEAR_CACHE = "clearcache";
+	private final String VMM_CLEAR_DATABASE = "cleardatabase";
+
+  // cgi args
+  private final String VMM_PAGE_ARG = "vmmPage";
+  private final String CURRENT_PAGE_ARG = "currentPage";
+  private final String FILE_TO_ADD_ARG = "fileToAdd";
+
+    protected void retrieveWizard(HashMap<String, Serializable> params,  String collection, String currentUsername,
+				  DerbyWrapper database,
+				  boolean prevPageNumFail, int prevPageNum, int pageNum, int highestVisitedPage,
+				  Document doc, Element response, Element responseMessage)
+    {
+	//Save given metadata
+	StringBuilder saveString = new StringBuilder("[");
+	Iterator<String> paramIter = params.keySet().iterator();
+	while (paramIter.hasNext())
+	    {
+		String paramName = paramIter.next();
+		if (paramName.startsWith(GSParams.MD_PREFIX))
+		    {
+			Object paramValue = params.get(paramName);
+			
+			if (paramValue instanceof String)
+			    {
+				saveString.append("{name:\"" + paramName + "\", value:\"" + (String) paramValue + "\"},");
+			    }
+			else if (paramValue instanceof HashMap)
+			    {
+				HashMap<String, String> subMap = (HashMap<String, String>) paramValue;
+				Iterator<String> subKeyIter = subMap.keySet().iterator();
+				while (subKeyIter.hasNext())
+				    {
+							String subName = subKeyIter.next();
+							saveString.append("{name:\"" + paramName + "." + subName + "\", value:\"" + subMap.get(subName) + "\"},");
+				    }
+			    }
+		    }
+	    }
+	if (saveString.length() > 2)
+	    {
+		saveString.deleteCharAt(saveString.length() - 1);
+		saveString.append("]");
+		
+		if (!prevPageNumFail)
+		    {
+			database.addUserData(currentUsername, "VMM___" + collection + "___" + prevPageNum + "___CACHED_VALUES", saveString.toString());
+		    }
+	    }
+	
+	//Construct the xsl
+	Document compiledVMManagerFile = null;
+	try
+	    {
+		compiledVMManagerFile = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+	    }
+	catch (Exception ex)
+	    {
+		ex.printStackTrace();
+	    }
+	Document vmmanagerBaseFile = GSXSLT.mergedXSLTDocumentCascade("vm-manager/vm-manager.xsl", (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), (ArrayList<String>) this.config_params.get(GSConstants.BASE_INTERFACES), false);
+	
+	Element numOfPagesElement = GSXML.getNamedElement(vmmanagerBaseFile.getDocumentElement(), "xsl:variable", "name", "numOfPages");
+	int numberOfPages = Integer.parseInt(numOfPagesElement.getTextContent());
+	
+	compiledVMManagerFile.appendChild(compiledVMManagerFile.importNode(vmmanagerBaseFile.getDocumentElement(), true));
+	
+	ArrayList<Document> pageDocs = new ArrayList<Document>();
+	ArrayList<String> pageNames = new ArrayList<String>();
+	for (int i = 1; i <= numberOfPages; i++)
+	    {
+		Document page = GSXSLT.mergedXSLTDocumentCascade("vm-manager/vmm_page" + i + ".xsl", (String) this.config_params.get(GSConstants.SITE_NAME), collection, (String) this.config_params.get(GSConstants.INTERFACE_NAME), (ArrayList<String>) this.config_params.get(GSConstants.BASE_INTERFACES), false);
+		pageDocs.add(page);
+		
+		Element pageTitleElem = (Element) GSXML.getNamedElement(page.getDocumentElement(), "xsl:variable", "name", "title");
+		pageNames.add(pageTitleElem.getTextContent());
+		
+		Element wizardPageElem = (Element) GSXML.getNamedElement(page.getDocumentElement(), "xsl:template", "name", "wizardPage");
+		wizardPageElem.setAttribute("name", "wizardPage" + i);
+		compiledVMManagerFile.getDocumentElement().appendChild(compiledVMManagerFile.importNode(wizardPageElem, true));
+	    }
+	
+	//Create the wizard bar
+	Element wizardBarTemplate = GSXML.getNamedElement(compiledVMManagerFile.getDocumentElement(), "xsl:template", "name", "wizardBar");
+	Element wizardBar = compiledVMManagerFile.createElement("ul");
+	wizardBar.setAttribute("id", "wizardBar");
+	wizardBarTemplate.appendChild(wizardBar);
+	
+	for (int i = 0; i < pageNames.size(); i++)
+	    {
+		String pageName = pageNames.get(i);
+		Element pageLi = compiledVMManagerFile.createElement("li");
+		if (pageNum == i + 1)
+		    {
+			pageLi.setAttribute("class", "wizardStepLink ui-state-active ui-corner-all");
+		    }
+		else if (i + 1 > highestVisitedPage + 1 && i + 1 > pageNum + 1)
+		    {
+			pageLi.setAttribute("class", "wizardStepLink ui-state-disabled ui-corner-all");
+		    }
+		else
+		    {
+			pageLi.setAttribute("class", "wizardStepLink ui-state-default ui-corner-all");
+		    }
+		Element link = compiledVMManagerFile.createElement("a");
+		pageLi.appendChild(link);
+		
+		link.setAttribute(GSXML.HREF_ATT, "javascript:;");
+		link.setAttribute("page", "" + (i + 1));
+		link.appendChild(compiledVMManagerFile.createTextNode(pageName));
+		wizardBar.appendChild(pageLi);
+	    }
+	
+	//Add a call-template call to the appropriate page in the xsl
+	Element mainDePageElem = GSXML.getNamedElement(compiledVMManagerFile.getDocumentElement(), "xsl:template", "match", "/page");
+	Element wizardContainer = GSXML.getNamedElement(mainDePageElem, "div", "id", "wizardContainer");
+	Element formContainer = GSXML.getNamedElement(wizardContainer, "form", "name", "vmmanager-form");
+	Element callToPage = compiledVMManagerFile.createElement("xsl:call-template");
+	callToPage.setAttribute("name", "wizardPage" + pageNum);
+	formContainer.appendChild(callToPage);
+	
+	Element cachedValueElement = doc.createElement("cachedValues");
+	response.appendChild(cachedValueElement);
+	try
+	    {
+		for (int i = pageNum; i > 0; i--)
+		    {
+			Element page = doc.createElement("pageCache");
+			page.setAttribute("pageNum", "" + i);
+			String cachedValues = database.getUserData(currentUsername, "VMM___" + collection + "___" + i + "___CACHED_VALUES");
+			if (cachedValues != null)
+			    {
+				page.appendChild(doc.createTextNode(cachedValues));
+				cachedValueElement.appendChild(page);
+			    }
+		    }
+	    }
+	catch (Exception ex)
+	    {
+		ex.printStackTrace();
+	    }
+	
+	try
+	    {
+		Transformer transformer = TransformerFactory.newInstance().newTransformer();
+		
+		File newFileDir = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "transform" + File.separator + "vm-manager");
+		newFileDir.mkdirs();
+		
+		File newFile = new File(newFileDir, File.separator + "compiledVMManager.xsl");
+		
+		//initialize StreamResult with File object to save to file
+		StreamResult sresult = new StreamResult(new FileWriter(newFile));
+		DOMSource source = new DOMSource(compiledVMManagerFile);
+		transformer.transform(source, sresult);
+	    }
+	catch (Exception ex)
+	    {
+		ex.printStackTrace();
+	    }
+	database.closeDatabase();
+    }
+
+    protected void createVM(HashMap<String, Serializable> params,  String collection, String currentUsername,
+			    String fileToAdd, File tempFile,
+			    DerbyWrapper database,
+			    boolean prevPageNumFail, int prevPageNum, int pageNum, int highestVisitedPage,
+			    Document doc, UserContext uc, Element response, Element responseMessage)
+
+    {
+		
+		HashMap<String, String> metadataMap = new HashMap<String, String>();
+		for (int i = pageNum; i > 0; i--)
+		    {
+			String cachedValues = database.getUserData(currentUsername, "VMM___" + collection + "___" + i + "___CACHED_VALUES");
+			if (cachedValues != null)
+			    {
+				Type type = new TypeToken<List<Map<String, String>>>()
+				    {
+				    }.getType();
+				
+				Gson gson = new Gson();
+				List<Map<String, String>> metadataList = gson.fromJson(cachedValues, type);
+				for (Map<String, String> metadata : metadataList)
+				    {
+					metadataMap.put(metadata.get("name"), metadata.get("value"));
+				    }
+			    }
+		    }
+		
+		String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE DirectoryMetadata SYSTEM \"https://greenstone.org/dtd/DirectoryMetadata/1.0/DirectoryMetadata.dtd\"><DirectoryMetadata><FileSet>";
+		xmlString += "<FileName>.*</FileName><Description>";
+		for (String key : metadataMap.keySet())
+		    {
+			xmlString += "<Metadata name=\"" + key.substring("MD___".length()) + "\" mode=\"accumulate\">" + metadataMap.get(key) + "</Metadata>";
+		    }
+		xmlString += "</Description></FileSet></DirectoryMetadata>";
+		
+		File metadataFile = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "import" + File.separator + fileToAdd + File.separator + "metadata.xml");
+		
+		try
+		    {
+			BufferedWriter bw = new BufferedWriter(new FileWriter(metadataFile));
+			bw.write(xmlString);
+			bw.close();
+		    }
+		catch (Exception ex)
+		    {
+			ex.printStackTrace();
+		    }
+		
+		Element buildMessage = doc.createElement(GSXML.MESSAGE_ELEM);
+		Element buildRequest = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, "ImportCollection", uc);
+		buildMessage.appendChild(buildRequest);
+		
+		Element paramListElem = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
+		buildRequest.appendChild(paramListElem);
+		
+		Element collectionParam = doc.createElement(GSXML.PARAM_ELEM);
+		paramListElem.appendChild(collectionParam);
+		collectionParam.setAttribute(GSXML.NAME_ATT, GSXML.COLLECTION_ATT);
+		collectionParam.setAttribute(GSXML.VALUE_ATT, collection);
+		
+		Element documentsParam = doc.createElement(GSXML.PARAM_ELEM);
+		paramListElem.appendChild(documentsParam);
+		documentsParam.setAttribute(GSXML.NAME_ATT, "documents");
+		documentsParam.setAttribute(GSXML.VALUE_ATT, fileToAdd);
+		
+		Element buildResponseMessage = (Element) this.mr.process(buildMessage);
+		
+		response.appendChild(doc.importNode(buildResponseMessage, true));
+	   
+    }
+    
+	public Node process(Node message)
+	{
+		Element request = (Element) GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
+		Document doc = request.getOwnerDocument();
+
+		UserContext uc = new UserContext((Element) request);
+		
+		Element responseMessage = doc.createElement(GSXML.MESSAGE_ELEM);
+		Element response = GSXML.createBasicResponse(doc, this.getClass().getSimpleName());
+		responseMessage.appendChild(response);
+
+		addSiteMetadata(response, uc);
+		addInterfaceOptions(response);
+
+		String currentUsername = uc.getUsername();
+		
+		// logger.debug("username="+username+", groups = "+groups);
+		if (currentUsername == null || currentUsername.equals("")) 
+		{
+		
+		  // TODO if user is not logged in, push to login page
+		  request.setAttribute("subaction", "");
+		  GSXML.addError(response, "You need to be logged in to use the VM Manager");
+		  return responseMessage;
+		}
+		
+		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
+		HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);
+
+		String collection = (String) params.get(GSParams.COLLECTION);
+
+		if (collection !=null && !collection.equals("")) {
+		  if (!userHasCollectionEditPermission(collection, uc)) {
+		    // we need to reset back to empty subaction here		    
+		    request.setAttribute("subaction", "");
+		    GSXML.addError(response, "You are not in the right group to access this collection. Please log in as a different user.");
+		    return responseMessage;
+		  
+		  }
+		}
+		int pageNum = -1;
+		boolean pageNumParseFail = false;
+		try
+		{
+			pageNum = Integer.parseInt(((String) params.get(VMM_PAGE_ARG)));
+		}
+		catch (Exception ex)
+		{
+			pageNumParseFail = true;
+		}
+
+		int prevPageNum = -1;
+		boolean prevPageNumFail = false;
+		try
+		{
+			prevPageNum = Integer.parseInt((String) params.get(CURRENT_PAGE_ARG));
+		}
+		catch (Exception ex)
+		{
+			prevPageNumFail = true;
+		}
+
+		DerbyWrapper database = new DerbyWrapper(GlobalProperties.getGSDL3Home() + File.separatorChar + "etc" + File.separatorChar + "usersDB");
+		if (pageNumParseFail)
+		{
+			try
+			{
+				pageNum = Integer.parseInt(database.getUserData(currentUsername, "VMM___" + collection + "___CACHED_PAGE"));
+			}
+			catch (Exception ex)
+			{
+				pageNum = 1;
+			}
+		}
+
+		int highestVisitedPage = -1;
+		String result = "";
+		int counter = 1;
+		while (result != null)
+		{
+			result = database.getUserData(currentUsername, "VMM___" + collection + "___" + counter + "___VISITED_PAGE");
+			if (result != null)
+			{
+				counter++;
+			}
+		}
+		highestVisitedPage = counter - 1;
+		if (highestVisitedPage == 0)
+		{
+			highestVisitedPage = 1;
+		}
+
+		if (pageNum > highestVisitedPage + 1)
+		{
+			pageNum = highestVisitedPage + 1;
+		}
+
+		database.addUserData(currentUsername, "VMM___" + collection + "___" + pageNum + "___VISITED_PAGE", "VISITED");
+
+		String subaction = ((Element) request).getAttribute(GSXML.SUBACTION_ATT);
+		if (subaction.toLowerCase().equals(VMM_RETRIEVE_WIZARD))
+		{
+		    // An improvement would be to put all the pageNum related variables into a public class/struct and
+		    // pass that instead
+		    retrieveWizard(params,collection,currentUsername,database,prevPageNumFail,prevPageNum,pageNum,highestVisitedPage,
+				   doc,response,responseMessage);
+			
+		}
+		else if (subaction.toLowerCase().equals(VMM_CREATE_VM))
+		{
+		    String fileToAdd = (String) params.get(FILE_TO_ADD_ARG);
+		    File tempFile = new File(GlobalProperties.getGSDL3Home() + File.separator + "tmp" + File.separator + fileToAdd);
+		    if (tempFile.exists())
+			{
+		    
+			    File newFileLocationDir = new File(GlobalProperties.getGSDL3Home() + File.separator + "sites" + File.separator + this.config_params.get(GSConstants.SITE_NAME) + File.separator + "collect" + File.separator + collection + File.separator + "import" + File.separator + fileToAdd);
+			    if (!newFileLocationDir.exists())
+				{
+				    newFileLocationDir.mkdir();
+				}
+			    File newFileLocation = new File(newFileLocationDir, fileToAdd);
+			    
+			    try
+				{
+				    FileUtils.copyFile(tempFile, newFileLocation);
+				}
+			    catch (Exception ex)
+				{
+				    ex.printStackTrace();
+				    GSXML.addError(responseMessage, "Failed to copy the deposited file into the collection.");
+				    return responseMessage;
+				}
+		    
+			    createVM(params,collection,currentUsername,
+				     fileToAdd, tempFile,
+				     database,prevPageNumFail,prevPageNum,pageNum,highestVisitedPage,
+				     doc,uc,response,responseMessage);
+			}
+		}
+		else if (subaction.toLowerCase().equals(VMM_CLEAR_CACHE))
+		{
+			database.clearUserDataWithPrefix(currentUsername, "VMM___");
+		}
+		else if (subaction.toLowerCase().equals(VMM_CLEAR_DATABASE))
+		{
+			database.clearUserData();
+			database.clearTrackerData();
+		}
+		else
+		{
+			Element vmmanagerPage = doc.createElement("vmmanagerPage");
+			response.appendChild(vmmanagerPage);
+
+			Element collList = getCollectionsInSiteForUser(uc);
+			vmmanagerPage.appendChild(doc.importNode(collList, true));
+		}
+
+		return responseMessage;
+	}
+
+    
+    public Element getCollectionsInSiteForUser(UserContext uc) 
+    {
+	Document doc = XMLConverter.newDOM();
+	Element message = doc.createElement(GSXML.MESSAGE_ELEM);
+	Element request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, "", new UserContext());
+	message.appendChild(request);
+	Element responseMessage = (Element) this.mr.process(message);
+	
+	Element response = (Element) GSXML.getChildByTagName(responseMessage, GSXML.RESPONSE_ELEM);
+	Element collectionList = (Element) GSXML.getChildByTagName(response, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
+	
+	Element validCollectionList = doc.createElement(GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
+
+	NodeList collections = GSXML.getChildrenByTagName(collectionList, GSXML.COLLECTION_ELEM);
+	for (int i = 0; i < collections.getLength(); i++) {
+	    String coll_name = ((Element)collections.item(i)).getAttribute(GSXML.NAME_ATT);
+	    if (userHasCollectionEditPermission(coll_name, uc)) {
+		// only add to the new list if the user has edit permission
+		validCollectionList.appendChild(doc.importNode(collections.item(i), true));
+		
+	    }
+	}
+	
+	return validCollectionList;
+    }
+
+  // collection must be non-null and non-empty
+  protected boolean userHasCollectionEditPermission(String collection, UserContext user_context) {
+
+    for (String group : user_context.getGroups()) {
+      // administrator always has permission
+      if (group.equals("administrator")) {
+	return true;
+      }
+      // all-collections-editor can edit any collection
+      
+      if (group.equals("all-collections-editor")) {
+	return true;
+      }
+      if (group.equals(collection+"-collection-editor")) {
+	  return true;
+      }
+    }
+    
+    // haven't found a group with edit permissions
+    return false;
+    
+  }
+}
