/* ======================================================================
The Bodington System Software License, Version 1.0

Copyright (c) 2001 The University of Leeds.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/

====================================================================== */

package org.bodington.util;

import java.util.Enumeration;
import java.util.Vector;

import org.bodington.database.PersistentObject;
import org.bodington.database.PrimaryKey;
import org.bodington.server.BuildingServer;
import org.bodington.server.BuildingServerException;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.ResourceTree;
import org.bodington.server.resources.UploadedFile;

/**
 * <p>Utility class for parsing URLs pointing to Bodington resources.
 * Provides methods for getting resource_id and uploadedFile_id from a URL</p>
 * Is there a reason we are using properties to store this information? Should
 * we not just be using the data from the request object?
 * @author A Corfield; WebLearn 01/12/2003
 * @author Matthew Buckett
 */
public class BodingtonURL
        {
          private String server, secureServer, openServer, secureServlet, openServlet, securePort, openPort;
          private String href = "href", equal = "=", quote = "\"";
          private Vector parseWarning;

          /**
           * Create a new BodingtonURL using the properties from the BuildingServer.
           */
          public BodingtonURL() {
              this(BuildingServer.getInstance().getProperties());
          }
          
          /**
           * Create a new BodingtonURL using the supplied properties.
           * @param props The properties to use in constructing our URLs.
           */
          public BodingtonURL(java.util.Properties props) {

            server = props.getProperty("buildingservlet.server");
            secureServer = "https://" + props.getProperty("buildingservlet.server");
            openServer = "http://" + props.getProperty("buildingservlet.server");
            secureServlet = props.getProperty("buildingservlet.secureservlet");
            openServlet = props.getProperty("buildingservlet.openservlet");
            securePort = props.getProperty("buildingservlet.port.https");
            openPort = props.getProperty("buildingservlet.port.http");
          }

        /**
         * Checks for any warnings after parsing html/text href attributes pointing to Bodington resources. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @return true if warnings.
         * @see #parseHtml(String)
         */
        public boolean isParseWarning() {
          return parseWarning.size() > 0;
        }

        /**
         * Get parse warning. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @return parseWarning a (String)vector containing warnings.
         * @see #parseHtml(String)
         */
        public Vector getParseWarning() {
          return parseWarning;
        }
        /**
         * Checks if resource has been placed in the recycling building. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param resource the resource to be checked.
         * @return true if resource has been deleted.
         * @throws BuildingServerException if recycling building not found.
         */
        public boolean isResourceDeleted(Resource resource) throws BuildingServerException {
          Resource recycler;
          Enumeration enumeration = null;

          if (resource == null)
            throw new BuildingServerException("Resource is null");

          enumeration = Resource.findResources("http_facility_no = 30", "left_index");
          if (!enumeration.hasMoreElements() || (recycler = (Resource) enumeration.nextElement()) == null)
            throw new BuildingServerException("Unable to locate the recycling building");
          return resource.isInside(recycler);
        }

        /**
         * Parses URLs that contain a reference to a Bodington resource or UploadedFile.
         * If the URL contains a query with res_id or file_id (e.g. ?res_id=1234), then the URL
         * is updated if the resource has been moved. If a Bodington URL does not contain the query part,
         * then the res_id or file_id is found and inserted into the URL. Parse errors resulting from
         * bad href attribute syntax or invalid/deleted resources are stored in a (String)Vector so
         * that they can be retrieved and reported. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param in String to be parsed.
         * @return String containing updated Bodington URls, if any.
         */
        public String parseHtml(String in) {

          PrimaryKey resId;
          String url, s;
          int i, j = 0;

          if (in == null)
            return null;
          if (in.trim().length() == 0)
            return in;

          StringBuffer out = new StringBuffer();
          parseWarning = new Vector();

          i = hrefStartIndex(in, j);
          while (i > -1) {
            out.append(in.substring(j, i));
            j = hrefEndIndex(in, i);
            if (j > -1) {
              s = in.substring(i, j).trim();
              url = getResourceUrl(s);
              if (url != null)
                s = url; // updates url by lookup via id
              else {
                url = getUploadedFileUrl(s);
                if (url != null)
                  s = url; // updates url by lookup via id
                else if (isBodingtonUrl(s)) {
                  resId = getResourceId(s);
                  if (resId != null) // get resource id if bodington resource
                    s += "?res_id=" + resId.toString();
                  else {
                    resId = getUploadedFileId(s);
                    if (resId != null) // get file id if uploaded file
                      s += "?file_id=" + resId.toString();
                    else {
                      parseWarning.add(s + " - cannot find this Bodington resource");
                    }
                  }
                }
              }
            } else break;
            out.append(s);
            i = hrefStartIndex(in, j + quote.length());
          }
          if (i < 0)
            out.append(in.substring(j));
          else if (j < 0) {
            out.append(in.substring(i));
            parseWarning.add(in.substring(i, i + 50) + " - error parsing HREF attribute");
          }
          return out.toString();
        }

        private int hrefStartIndex(String in, int fromIndex) {

          String s;
          int i, j, k, m, n;
          if (in == null || fromIndex < 0 || fromIndex > in.length() - 1) {
            parseWarning.add("Error parsing html/text");
            return -1;
          }
          i = in.indexOf(href, fromIndex);
          if (i < 0)
            return -1;
          j = in.indexOf(quote, i + href.length());
          if (j < 0) {
            parseWarning.add(in.substring(i, i + 50) + " - error parsing HREF attribute");
            return -1;
          }
          k = i + href.length();
          if (!in.substring(k, j).trim().equals(equal)) {
            parseWarning.add(in.substring(i, i + 50) + " - error parsing HREF attribute");
            return -1;
          }
          k = j + quote.length();
          m = in.indexOf(quote, k);
          if (m < 0) {
            parseWarning.add(in.substring(i, i + 50) + " - error parsing HREF attribute");
            return -1;
          }
          n = in.indexOf(" ", k);
          if (n > -1 && n < m) {
            parseWarning.add(in.substring(i, i + 50) + " - error parsing HREF attribute");
            return -1;
          }

          return k;
        }

        private int hrefEndIndex(String in, int fromIndex) {

          return in.indexOf(quote, fromIndex);
        }

          private String getResourceUrl(String s) {

            Resource res;
            String url;
            int i, id;
            boolean deleted = false;

            i = s.indexOf("?res_id=");
            if (i < 0)
              return null;

            try {
              id = Integer.parseInt(s.substring(i + 8));
            } catch (NumberFormatException ex) {
              parseWarning.add(s + " - the res_id is not a valid integer");
              return null;
            }

            res = getResource(new PrimaryKey(id));
            if (res == null) {
              parseWarning.add(s + " - resource not found");
              return null;
            }

            try {
              deleted = isResourceDeleted(res);
            } catch (BuildingServerException ex1) {}
            if (deleted) {
              parseWarning.add(s + " - resource deleted");
              return null;
            }

            url = getResourceUrl(res);
            if (url == null) {
              parseWarning.add(s + " - cannot find the name for this resource");
              return null;
            }
            return url + s.substring(i);
          }

          private String getUploadedFileUrl(String s) {

            Resource res;
            UploadedFile ulf;
            String url;
            int i, id;
            boolean deleted = false;

            i = s.indexOf("?file_id=");
            if (i < 0)
              return null;

            try {
              id = Integer.parseInt(s.substring(i + 9));
            } catch (NumberFormatException ex) {
              parseWarning.add(s + " - the file_id is not a valid integer");
              return null;
            }

            ulf = getUploadedFile(new PrimaryKey(id));
            if (ulf == null) {
              parseWarning.add(s + " - uploaded file not found");
              return null;
            }

            if (ulf.isDeleted()) {
              parseWarning.add(s + " - uploaded file deleted");
              return null;
            }

            res = getResource(ulf.getResourceId());
            try {
              deleted = isResourceDeleted(res);
            } catch (BuildingServerException ex1) {}
            if (deleted) {
              parseWarning.add(s + " - uploadedFile parent resource deleted");
              return null;
            }

            url = getUploadedFileUrl(res, ulf);
            if (url == null) {
              parseWarning.add(s + " - cannot find the name for this uploaded file");
              return null;
            }
            return url + s.substring(i);
          }

        /**
         * Gets a Resource from a Bodington URL. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url pointing to Bodington resource.
         * @return Resource or Null if not found.
         */
          public Resource getResource(String url) {
            String name = getResourceName(url);
            if (name == null)
              return null;

            try {
              return ResourceTree.getInstance().findResource(name);
            } catch (BuildingServerException ex) {
              return null;
            }
          }

        /**
         * Gets a Resource via resource_id. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param resource_id for resource.
         * @return Resource or Null if not found.
         */
          public Resource getResource(PrimaryKey resource_id) {
            if (resource_id == null)
              return null;
            try {
              PersistentObject obj = PersistentObject.findPersistentObject(resource_id, "org.bodington.server.resources.Resource");
              if (obj == null || !(obj instanceof Resource))
                return null;
              return (Resource)obj;
            } catch (BuildingServerException ex) {
              return null;
            }
          }

          private PrimaryKey getResourceId(String url) {
            Resource res = null;
            boolean deleted = false;

            res = getResource(url);
            if (res == null)
              return null;

            try {
              deleted = isResourceDeleted(res);
            } catch (BuildingServerException ex1) {}
            if (deleted) {
              parseWarning.add(url + " - this Bodington resource has been deleted");
              return null;
            }
            return res.getResourceId();
          }

        /**
         * Gets UploadedFile name from a Bodington URL. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url pointing to Bodington UploadedFile.
         * @return String the UploadedFile name.
         */
          public String getUploadedFileName(String url) {
            UploadedFile ulf;

            try {
              ulf = getUploadedFile(url);
              if (ulf == null)
                return null;
              return ulf.getNameInResource();
            } catch (BuildingServerException ex) {
              return null;
            }
          }
        /**
         * Gets an UploadedFile via file_id. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param file_id for UploadedFile.
         * @return UploadedFile found by file_id or Null if not found.
         */
          public UploadedFile getUploadedFile(PrimaryKey file_id) {

            if (file_id == null)
              return null;
            try {
              PersistentObject obj = PersistentObject.findPersistentObject(file_id, "org.bodington.server.resources.UploadedFile");
              if (obj == null || !(obj instanceof UploadedFile))
                return null;
              return (UploadedFile)obj;
            } catch (BuildingServerException ex) {
              return null;
            }
          }
        /**
         * Gets an UploadedFile from a Bodington URL. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url pointing to an UploadedFile.
         * @return UploadedFile or Null if not found.
         */
          public UploadedFile getUploadedFile(String url) {
            Resource res;
            PrimaryKey resId;
            UploadedFile ulf;
            String name;
            int i;

            res = getUploadedFileResource(url);
            if (res == null)
              return null;

            try {
              resId = res.getResourceId();
              name = res.getFullName();
              i = url.indexOf(name);
              if (i < 0)
                return null;

              i += name.length();
              name = url.substring(i);
              return UploadedFile.findUploadedFileByPath(resId, name);
            } catch (BuildingServerException ex) {
              return null;
            }
          }

          private PrimaryKey getUploadedFileId(String url) {
            UploadedFile ulf = null;

            ulf = getUploadedFile(url);
            if (ulf == null)
              return null;
            if (ulf.isDeleted())
              parseWarning.add(url + " - this uploaded file has been deleted");
            return ulf.getPrimaryKey();
          }
        /**
         * Gets the Resource for an UploadedFile from a URL pointing to an UploadedFile. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url pointing to Bodington resource.
         * @return Resource found by Bodington URL or Null if not found.
         */
          public Resource getUploadedFileResource(String url) {
            Resource res;
            String name;
            int i;

            if (url == null || (url = url.trim()).length() == 0)
              return null;

            if (url.endsWith("/"))
              url = url.substring(0, url.length() - 1);

            i = url.lastIndexOf("/");
            if (i < 0)
              return null;

            url = url.substring(0, i + 1);
            name = getResourceName(url);
            if (name == null)
              return null;

            try {
              res = ResourceTree.getInstance().findResource(name);
              if (res == null)
                return getUploadedFileResource(url);
            } catch (BuildingServerException ex) {
              return null;
            }
            return res;
          }

        /**
         * Gets the Bodington URL for a Resource. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param resource_id PrimaryKey for Bodington Resource.
         * @param checkStatus when true checks if Resource has been put in the recycle building i.e. deleted.
         * @return String the Bodington URL or null if the resource does not exist or has been deleted.
         */
          public String getResourceUrl(PrimaryKey resource_id, boolean checkStatus) {

            Resource res;

            if (resource_id == null)
              return null;

            res = getResource(resource_id);
            if (res == null)
              return null;

            if (checkStatus) {
              try {
                if (isResourceDeleted(res))
                  return null;
              } catch (BuildingServerException ex) {
                return null;
              }
            }
            return getResourceUrl(res);
          }

        /**
         * Gets the Bodington URL for an UploadedFile. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param file_id PrimaryKey for UploadedFile.
         * @param checkStatus if true checks if UploadedFile has been put in the recycle building i.e. deleted.
         * @return String the Bodington URL or null if the UploadedFile does not exist or has been deleted.
         */
          public String getUploadedFileUrl(PrimaryKey file_id, boolean checkStatus) {
            PrimaryKey resource_id;
            Resource resource;
            UploadedFile ulf;

            if (file_id == null)
              return null;

            ulf = getUploadedFile(file_id);
            if (ulf == null)
              return null;

            if (checkStatus && ulf.isDeleted())
              return null;

            resource_id = ulf.getResourceId();
            resource = getResource(resource_id);
            if (resource == null)
              return null;

            return getUploadedFileUrl(resource, ulf);
          }

        /**
         * Gets the Bodington URL from an UploadedFile. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param ulf the UploadedFile.
         * @return String the Bodington URL for this UploadedFile.
         */
          public String getUploadedFileUrl(UploadedFile ulf) {
            PrimaryKey resource_id;
            Resource resource;
            String path, resName, ulfName;
            StringBuffer url = new StringBuffer();

            if (ulf == null )
              return null;

            path = getPath(false);
            try {
              resource_id = ulf.getResourceId();
              resource = getResource(resource_id);
              if (resource == null)
                return null;

              resName = resource.getFullName();
              ulfName = ulf.getNameInResource();
            } catch (BuildingServerException ex) {
              return null;
            }
            if (path.endsWith("/") && resName.startsWith("/"))
              path = path.substring(0, path.length() - 1);
            url.append(path);
            url.append(resName);
            url.append(ulfName);
            return url.toString();
          }

        /**
         * Gets the Bodington URL from an UploadedFile and it's parent Resource. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param resource the UploadedFile parent Resource.
         * @param ulf the UploadedFile.
         * @return String the Bodington URL for this UploadedFile.
         */
          public String getUploadedFileUrl(Resource resource, UploadedFile ulf) {
            PrimaryKey resource_id;
            String path, resName, ulfName;
            StringBuffer url = new StringBuffer();

            if (resource == null || ulf == null )
                return null;

            path = getPath(false);
            try {
              resName = resource.getFullName();
              ulfName = ulf.getNameInResource();
            } catch (BuildingServerException ex) {
              return null;
            }
            if (path.endsWith("/") && resName.startsWith("/"))
              path = path.substring(0, path.length() - 1);
            url.append(path);
            url.append(resName);
            url.append(ulfName);
            return url.toString();

          }

        /**
         * Gets the URL from a Bodington Resource. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param resource the Bodington Resource.
         * @return String the Bodington URL for this Resource.
         */
          public String getResourceUrl(Resource resource) {
            String path, name;

            if (resource == null)
                return null;

            StringBuffer url = new StringBuffer();
            path = getPath(false);
            try {
              name = resource.getFullName();
              if (path.endsWith("/") && name.startsWith("/"))
                path = path.substring(0, path.length() - 1);
              url.append(path);
              url.append(name);
              return url.toString();
            } catch (BuildingServerException ex) {
              return null;
            }
          }
          
          /**
           * Get the path to the start of the Bodington installation. 
           * @param secure If the URL should be a https one.
           * @return A string for the URL.
           */
          private String getPath(boolean secure)
          {
              StringBuffer string = new StringBuffer();
              if (secure)
              {
                  string.append("https://");
                  string.append(server);
                  string.append(securePort.equals("443")?"":(":"+ securePort));
                  string.append(secureServlet);
              }
              else 
              {
                  string.append("http://");
                  string.append(server);
                  string.append(securePort.equals("80")?"":(":"+openPort));
                  string.append(secureServlet);
              }
              return string.toString();
          }


        /**
         * Checks if a URL points to the Bodington site. Use 
         * {@link #isValidBodingtonUrl(String)} to test if a Resource or 
         * UploadedFile within the Bodington site exists. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url the Bodington URL.
         * @return true if url points to the Bodington site.
         */
          public boolean isBodingtonUrl(String url) {

            String name = getResourceName(url);
            if (name == null)
              return false;
            return true;
          }

        /**
         * Checks if a URL points to a Resource or UploadedFile within the Bodington site. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url the Bodington URL.
         * @return true if url points to a Resource or UploadedFile within the Bodington site.
         */
        public boolean isValidBodingtonUrl(String url) {

          String name = getResourceName(url);
          if (name == null)
            return false;
          if (getResource(url) != null)
            return true;
          if (getUploadedFile(url) != null)
            return true;
          return false;
        }

        /**
         * Gets Resource name from a Bodington URL. <p>
         * <i>(WebLearn; A Corfield 01/12/2003)</i>
         * @param url pointing to Bodington Resource.
         * @return String the Resource name.
         */
        public String getResourceName(String url) {

          String port;
          int i;
          if (url == null || (url = url.trim()).length() == 0)
              return null;

          if (url.startsWith(secureServer)) {
            i = secureServer.length();
            port = securePort;
          } else if (url.startsWith(openServer)) {
            i = openServer.length();
            port = openPort;
          } else
            return null;

          if (url.substring(i).startsWith(":")) {
            if (url.substring(i).startsWith(":" + port))
              i += port.length() + 1;
            else
              return null;
          }

          if (url.substring(i).startsWith(secureServlet))
            i += secureServlet.length();
          else if (url.substring(i).startsWith(openServlet))
            i += openServlet.length();
          else
            return null;

          return url.substring(i - 1);
        }
      }