import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/** Apache HttpClient is required **/
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class FMEServerRESTAPIDemo
{
      private HttpClient            client_ = null;
      // Used to read content from the user
      private BufferedReader        br_            = null;
      // Security token for authentication
      private String                token_         = null;
      
      private String                host_          = null;
      private int                   port_          = 0;

      public FMEServerRESTAPIDemo(String host, int port) throws Exception
      {
         client_ = new HttpClient();
         br_ = new BufferedReader(new InputStreamReader(System.in));

         host_ = host;
         port_ = port;
         
         try
         {
            getConnectionInfo();
         }
         catch(IOException e)
         {
            System.err.println(e.getMessage());
         }
      }

      public void getConnectionInfo() throws Exception
      {
         System.out.println("Please enter server connection credentials.");
         System.out.print("UserId:");
         String userid_ = br_.readLine();

         if(!userid_.equals(""))
         {
            System.out.print("Password:");
            String password_ = br_.readLine();
            // We must first generate a security token for authentication purposes
            String fmeUrl = "http://"+host_+":"+port_+"/fmetoken/generate";

            PostMethod method = new PostMethod(fmeUrl);
            method.addParameter("user", userid_);
            method.addParameter("password", password_);
            method.addParameter("expiration", "1");
            method.addParameter("timeunit", "hour");
            
            if (client_.executeMethod(method) == 200) 
            {
               token_ = method.getResponseBodyAsString();
            }
            else 
            {
               throw new Exception("Authentication failed");
            }
         }
      }

      public void execute()
      {
         displayMainMenu();
         try
         {
            int choice = getMenuChoice();
            executeMenuChoice(choice);
         }
         catch(NumberFormatException e)
         {
            System.err.println("Invalid input!");
         }
         catch(Exception e)
         {
            System.err.println(e.getMessage());
         }
      }

      private void displayMainMenu()
      {
         System.out.print("\n\n" + "===FME Server REST API Demo===\n"
               + "1. List available repositories\n" + "2. Add a repository\n"
               + "3. Remove a repository\n" + "4. List available workspaces\n"
               + "5. Add a workspace\n" + "6. Update a workspace\n"
               + "7. Get a workspace\n" + "8. Remove a workspace\n"
               + "9. List resources of a workspace\n" + "10. Add a resource\n"
               + "11. Update a resource\n" + "12. Get a resource\n"
               + "13. Remove a resource\n" + "14. List available services\n"
               + "15. Add a service\n" + "16. Update a service\n"
               + "17. Remove a service\n" + "18. Run a workspace\n" 
               + "19. List published parameters of a workspace\n"
               + "20. List jobs\n" + "21. View a job\n" + "22. Remove finished jobs\n" 
               + "23. Cancel a queued job\n" + "0. Quit\n"
               + "\nSELECT: ");
      }

      private int getMenuChoice() throws NumberFormatException, IOException
      {
         return Integer.parseInt(br_.readLine());
      }

      private void executeMenuChoice(int choice) throws Exception
      {
         switch(choice)
         {
         case 1:
            listRepositories();
            break;
         case 2:
            addRepository();
            break;
         case 3:
            removeRepository();
            break;
         case 4:
            listWorkspaces();
            break;
         case 5:
            addWorkspace();
            break;
         case 6:
            updateWorkspace();
            break;
         case 7:
            getWorkspace();
            break;
         case 8:
            removeWorkspace();
            break;
         case 9:
            listResources();
            break;
         case 10:
            addResource();
            break;
         case 11:
            updateResource();
            break;
         case 12:
            getResource();
            break;
         case 13:
            removeResource();
            break;
         case 14:
            listServices();
            break;
         case 15:
            addService();
            break;
         case 16:
            updateService();
            break;
         case 17:
            removeService();
            break;
         case 18:
            runWorkspace();
            break;
         case 19:
            listParameters();
            break;
         case 20:
            listJobs();
            break;
         case 21:
            viewJob();
            break;
         case 22:
            removeCompletedJobs();
            break;
         case 23:
            cancelJob();
            break;
         case 0:
            System.exit(0);
         default:
            System.err.println("Invalid option!");
            return;
         }
         System.out.println("\nThe operation was a success");
      }
      
      private Document parseXML(String contents) throws Exception
      {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         DocumentBuilder db = dbf.newDocumentBuilder();
         InputSource is = new InputSource();
         is.setCharacterStream(new StringReader(contents));
         Document doc = db.parse(is);
         return doc;
      }
      
      private void listJobs() throws Exception 
      {
         // We would like to see the IDs of all jobs listed
         // So, we perform the same action for each job type
         String[] categories = {
               "running",
               "completed",
               "scheduled",
               "queued"
         };
         
         for (String category : categories) 
         {
            String fmeUrl = "http://"+host_+":"+port_+"/fmerest/jobs/"+category+".xml?token="+token_;
            GetMethod method = new GetMethod(fmeUrl);
            
            client_.executeMethod(method);
            String contents = method.getResponseBodyAsString();

            Document doc = parseXML(contents);
            NodeList nl = doc.getElementsByTagName("job");

            System.out.println(category + ":");
            
            for (int s = 0; s < nl.getLength(); s++) 
            {
               System.out.println(((Element)nl.item(s)).getElementsByTagName("ID").item(0).getTextContent());
            }
         }
      }
      
      private void viewJob() throws Exception 
      {
         String id = getInput("Please enter the id of the job to view");
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/jobs/"+id+".xml?token="+token_;
         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();
         
         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("job");

         for (int s = 0; s < nl.getLength(); s++) 
         {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("ID").item(0).getTextContent());
            // add any extra things to view here;
         }
      }

      private void removeCompletedJobs() throws Exception
      {
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/jobs/completed.xml?token="+token_;

         DeleteMethod method = new DeleteMethod(fmeUrl);
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Job deletion failed");
         }
      }
      
      private void cancelJob() throws Exception 
      {
         String id = getInput("Please enter the id of the job to cancel");
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/jobs/"+id+"/cancel.xml?token="+token_;
         
         PostMethod method = new PostMethod(fmeUrl);
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Job cancellation failed");
         }
      }
      
      private void listRepositories() throws Exception
      {
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories.xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();
         
         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("repository");

         for (int s = 0; s < nl.getLength(); s++) {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }
      }

      private void addRepository() throws Exception
      {
         String name = getInput("Please enter the name for the new repository:");
         String description = getInput("Please enter the description for the new repository:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories.xml?token="+token_;
         PostMethod method = new PostMethod(fmeUrl);

         method.addParameter("repository", name);
         method.addParameter("description", description);
         
         if (client_.executeMethod(method) != 200) 
         {
            System.err.println(method.getResponseBodyAsString());
            throw new Exception("Repository creation failed");
         }
      }

      private void removeRepository() throws Exception
      {
         String name = getInput("Please enter the name for the repository to remove:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+name+".xml?token="+token_;
         DeleteMethod method = new DeleteMethod(fmeUrl);

         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Repository deletion failed");
         }
      }

      private void listWorkspaces() throws Exception
      {
         String name = getInput("Please enter the name for the repository show the workspaces for:");
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+name+".xml?token="+token_;
         GetMethod method = new GetMethod(fmeUrl);
         
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("workspace");

         for (int s = 0; s < nl.getLength(); s++) {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }       
      }

      private void addWorkspace() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the new workspace:");
         String workspaceFilePath = getInput("Please enter the file path for the new workspace to add:");

         String workspaceName = new File(workspaceFilePath).getName();

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+".xml?token="+token_;
         PutMethod method = new PutMethod(fmeUrl);
         
         method.setRequestEntity(
             new FileRequestEntity(
                   new File(workspaceFilePath), "doesn't matter")
             );
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Adding workspace failed");
         }
      }

      private void updateWorkspace() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the workspace to update:");
         String workspaceFilePath = getInput("Please enter the file path for the workspace to update:");

         String workspaceName = new File(workspaceFilePath).getName();

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+".xml?token="+token_;
         
         PutMethod method = new PutMethod(fmeUrl);
         
         method.setRequestEntity(
             new FileRequestEntity(
                   new File(workspaceFilePath), "doesn't matter")
             );
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Adding workspace failed");
         }       
      }

      private void getWorkspace() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the workspace to get:");
         String workspaceName = getInput("Please enter the name of the workspace to get:");
         String workspaceFilePath = getInput("Please enter the local file path for the workspace to be saved at:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+".xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);

         FileOutputStream file = new FileOutputStream(workspaceFilePath);
         PrintStream fileStream = new PrintStream(file);
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Getting workspace failed");
         }   
         
         byte[] responseBody = method.getResponseBody();

         // Deal with the response.
         // Use caution: ensure correct character encoding and is not binary data
         fileStream.println(new String(responseBody));
         
         fileStream.close();
         file.close();
      }

      private void removeWorkspace() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the workspace to remove:");
         String workspaceName = getInput("Please enter the name of the workspace to remove:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+".xml?token="+token_;

         DeleteMethod method = new DeleteMethod(fmeUrl);
         if (client_.executeMethod(method) != 200)
         {
            throw new Exception("Workspace deletion failed");
         }           
      }

      private void listResources() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the resources:");
         String workspaceName = getInput("Please enter the workspace name for the resources:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/resources.xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);

         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("resource");

         for (int s = 0; s < nl.getLength(); s++) {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }        
      }

      private void addResource() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the new resource:");
         String workspaceName = getInput("Please enter the workspace name for the new resource:");
         String resourceFilePath = getInput("Please enter the file path for the new resource to add:");

         String resourceName = new File(resourceFilePath).getName();

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/resources/"+resourceName+".xml?token="+token_;

         PutMethod method = new PutMethod(fmeUrl);

         method.setRequestEntity(
             new FileRequestEntity(
                   new File(resourceFilePath), "")
             );
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Resource addition failed");
         }
      }

      private void updateResource() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the resource to update:");
         String workspaceName = getInput("Please enter the workspace name for the resource to update:");
         String resourceFilePath = getInput("Please enter the file path for the resource to update:");

         String resourceName = new File(resourceFilePath).getName();

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/resources/"+resourceName+".xml?token="+token_;

         PutMethod method = new PutMethod(fmeUrl);

         method.setRequestEntity(
             new FileRequestEntity(
                   new File(resourceFilePath), "")
             );
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Resource addition failed");
         }        
      }

      private void getResource() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the resource to get:");
         String workspaceName = getInput("Please enter the workspace name for the resource to get:");
         String resourceName = getInput("Please enter the resource name for the resource to get:");
         String resourceFilePath = getInput("Please enter the local file path for the resource to get:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/resources/"+resourceName+".xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);

         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Resource download failed");
         }    

         FileOutputStream file = new FileOutputStream(resourceFilePath);
         PrintStream fileStream = new PrintStream(file);
         byte[] responseBody = method.getResponseBody();

         // Deal with the response.
         // Use caution: ensure correct character encoding and is not binary data
         fileStream.println(new String(responseBody));
         
         fileStream.close();
         file.close();
      }

      private void removeResource() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the resource to remove:");
         String workspaceName = getInput("Please enter the workspace name for the resource to remove:");
         String resourceName = getInput("Please enter the resource name for the resource to remove:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/resources/"+resourceName+".xml?token="+token_;

         DeleteMethod method = new DeleteMethod(fmeUrl);

         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Resource deletion failed");
         }         
      }

      private void listServices() throws Exception
      {
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/services.xml?token="+token_;

         GetMethod method = new GetMethod(fmeUrl);

         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();
         
         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("service");
         
         for (int s = 0; s < nl.getLength(); s++) {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }         
      }

      private void addService() throws Exception
      {
         String name = getInput("Please enter the name of the new service:");
         String urlPattern = getInput("Please enter the URL pattern of the new service:");
         String displayName = getInput("Please enter the display name of the new service:");
         String description = getInput("Please enter the description of the new service:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/services.xml";
   
         PostMethod method = new PostMethod(fmeUrl);
         method.addParameter("token", token_);
         method.addParameter("service", name);
         method.addParameter("description", description);
         method.addParameter("displayname", displayName);
         method.addParameter("urlpattern", urlPattern);
         method.addParameter("isenabled", "true");
         method.addParameter("isregallowed", "true");
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Service creation failed");
         }
      }

      private void updateService() throws Exception
      {
         String name = getInput("Please enter the name of the service to update:");
         String urlPattern = getInput("Please enter the URL pattern of the service to update:");
         String displayName = getInput("Please enter the display name of the service to update:");
         String description = getInput("Please enter the description of the service to update:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/services.xml";
         
         PostMethod method = new PostMethod(fmeUrl);
         method.addParameter("token", token_);
         method.addParameter("service", name);
         method.addParameter("description", description);
         method.addParameter("displayname", displayName);
         method.addParameter("urlpattern", urlPattern);
         method.addParameter("isenabled", "true");
         method.addParameter("isregallowed", "true");
         
         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Service update failed");
         }        
      }

      private void removeService() throws Exception
      {
         String name = getInput("Please enter the name of the service to remove:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/services/"+name+".xml?token="+token_;

         DeleteMethod method = new DeleteMethod(fmeUrl);

         if (client_.executeMethod(method) != 200) 
         {
            throw new Exception("Service deletion failed");
         }            
      }

      /** cleanup **/
      private void runWorkspace() throws Exception
      {
         String repositoryName = getInput("Please enter the repository name for the workspace to run:");
         String workspaceName = getInput("Please enter the name of the workspace to run:");

         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/run.xml";
         
         PostMethod method = new PostMethod(fmeUrl);
         method.addParameter("token", token_);
         // add published parameters

         System.out.println("Sending a transformation request to the FME Server...\n");

         // Submit the transformation request. This method will block until
         // transformation results are available. (Users who wish to submit jobs
         // asynchronously should instead use set the parameter to asynchronous method.) 
         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();
         
         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("fmeServerResponse");
         
         System.out.println("The following information was parsed from the response:\n");
         System.out.println("============");
         for (int s = 0; s < nl.getLength(); s++) 
         {
            System.out.println("Job Status: "+((Element)nl.item(s)).getElementsByTagName("jobStatus").item(0).getTextContent());
            System.out.println("Job Id: "+((Element)nl.item(s)).getElementsByTagName("id").item(0).getTextContent());
         }
         


  //       if(!result.getTransformationSuccess())
         {

            // The transformation was not successful. We inform the user of this
            // and then use getStatusMessage() to get the error message that was
            // returned by the server (e.g., "File austin.fmw could not be found.")

  //          throw new FMEServerException("Transformation failed.\nThe error was: "
  //                + result.getStatusMessage());
         }
   //      else
         {
            // The transformation was successful. We inform the user of this.
            System.out.println("Transformation successful!");

            // The transformation result object contains many name-value pairs that
            // are extracted from the SUCCESS_RESPONSE defined in the FME Server
            // node subsection. In addition to these pairs, there are also several
            // name-value pairs added by the server: the time that the job was sent
            // to an FME Server node for processing (timeStarted), the job ID (id),
            // etcetera. The getAllProperties() method returns a map of all these
            // name-value pairs. Here, we iterate over this map -- using its entry
            // set -- and simply print out its contents in the form "name = value".


  //          for(Map.Entry<String, String> prop : result.getAllProperties()
  //                .entrySet())
            {

               // It is possible for keys to be present that have a null value (for
               // example, "priority" will map to null if no priority was specified
               // for this job). We want to skip over such name-value pairs,
               // printing out only those pairs for which a non-null value exists.
               // We filter the pairs by checking whether the length of the value
               // string is greater than 0 -- i.e., that a useful value exists:

   //            if(prop.getValue().length() > 0)
               {
   //               System.out.println(prop.getKey() + " = " + prop.getValue());
               }
            }
            System.out.println("============");
         }
      }
      
      public void listParameters() throws Exception {

         String repositoryName = getInput("Please enter the repository name for the workspace to view the parameters of:");
         String workspaceName = getInput("Please enter the name of the workspace to view the parameters of:");
         
         String fmeUrl = "http://"+host_+":"+port_+"/fmerest/repositories/"+repositoryName+"/"+workspaceName+"/parameters.xml?token="+token_;
         
         GetMethod method = new GetMethod(fmeUrl);

         client_.executeMethod(method);
         String contents = method.getResponseBodyAsString();

         Document doc = parseXML(contents);
         NodeList nl = doc.getElementsByTagName("parameter");
         
         for (int s = 0; s < nl.getLength(); s++)
         {
            System.out.println(((Element)nl.item(s)).getElementsByTagName("name").item(0).getTextContent());
         }
      }

      private String getInput(String promptMessage) throws Exception
      {
         System.out.print(promptMessage + "\t");
         return br_.readLine();
      }

      /**
       * This program accepts two parameters. First parameter is the FME Server
       * host. ie. localhost Second parameter is the FME Server port. ie. 7071
       */
      public static void main(String args[]) throws Exception
      {
         String host = null;
         int port = 0;

         // Extract the arguments from the command line
         if(args.length != 2)
         {
            argError();
         }
         else
         {
            host = args[0];
            try
            {
               port = Integer.parseInt(args[1]);
            }
            catch(NumberFormatException nfe)
            {
               argError();
            }
         }

         FMEServerRESTAPIDemo demo = new FMEServerRESTAPIDemo(host, port);

         while(true)
         {
            demo.execute();
         }
      }

      private static void argError()
      {
         System.err.println("USAGE:\n\tFMEServerRESTAPIDemo <host> <port>");
         System.exit(-1);
      }
}
