// vi: set ts=3 sw=3:
/* 
 * $Header: /home/cvspublic/jakarta-slide/webdavclient/ant/src/java/org/apache/webdav/ant/Utils.java,v 1.10 2005/02/21 16:33:03 luetzkendorf Exp $
 * $Revision: 1.10 $
 * $Date: 2005/02/21 16:33:03 $
 * ========================================================================
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================================
 */
package org.apache.webdav.ant;

import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Stack;
import java.util.Vector;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.HttpURL;
import org.apache.commons.httpclient.HttpsURL;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PutMethod;

import org.apache.tools.ant.BuildException;
import org.apache.webdav.lib.Property;
import org.apache.webdav.lib.PropertyName;
import org.apache.webdav.lib.ResponseEntity;
import org.apache.webdav.lib.WebdavException;
import org.apache.webdav.lib.methods.CopyMethod;
import org.apache.webdav.lib.methods.LockMethod;
import org.apache.webdav.lib.methods.MkcolMethod;
import org.apache.webdav.lib.methods.MoveMethod;
import org.apache.webdav.lib.methods.PropFindMethod;
import org.apache.webdav.lib.methods.UnlockMethod;
import org.apache.webdav.lib.properties.ResourceTypeProperty;
import org.apache.webdav.lib.util.WebdavStatus;

/**
 */
public class Utils {
   public static final String DAV_NAMESPACE= "DAV:";
   
   public static final PropertyName DISPLAYNAME = new PropertyName(
         DAV_NAMESPACE, "displayname"); 

   public static final PropertyName GETLASTMODIFIED = new PropertyName(
         DAV_NAMESPACE, "getlastmodified"); 
   
   public static final DateFormat GETLASTMODIFIED_FORMAT = 
            new SimpleDateFormat("EEE, d MMM yyyy kk:mm:ss z", Locale.US);

   public static final PropertyName RESOURCETYPE = new PropertyName(
         DAV_NAMESPACE, "resourcetype"); 

   /**
    * Lookup for given property in a propfind response.
    * @param propFind the method, must always be rxecuted
    * @param name
    * @param path path of the item for which the property is searched for
    * @return the property of <code>null</code> if not found
    */
   public static Property findProperty(PropFindMethod propFind, 
                                        PropertyName name,
                                        String path) 
   {
      Enumeration e = propFind.getResponseProperties(path);
      Property p = findProperty(e, name);
      // a collection requested as /a/col/path/ may be as 
      // /a/col/path in the response
      if (p == null && path.endsWith("/")) {
         e = propFind.getResponseProperties(path.substring(0, path.length()-1));
         p = findProperty(e, name);
      }
      return p;
   }
   
   /**
    * Searches in the enumeration of Propery objects for a property with the 
    * given name. 
    * @param e enumeration of Property objects.
    * @param name
    * @return the property searched for of <code>null</code> if not found.
    */
   public static Property findProperty(Enumeration e, PropertyName name) {
      while (e.hasMoreElements()) {
         Property p = (Property)e.nextElement();
         
         if (p.getNamespaceURI().equals(name.getNamespaceURI()) &&
             p.getLocalName().equals(name.getLocalName())) 
         {
            return p;
         }
      }
      return null;
   }
   
   /**
    * Returns <code>true</code> if the resource given as URL does exist.
    * @param client
    * @param httpURL
    * @return <code>true</code>if the resource exists
    * @throws IOException
    * @throws HttpException
    */
   public static boolean resourceExists(HttpClient client, HttpURL httpURL)
      throws IOException, HttpException
   {
      HeadMethod head = new HeadMethod(httpURL.getEscapedURI());
      head.setFollowRedirects(true);
      int status = client.executeMethod(head);
      
      switch (status) {
         case WebdavStatus.SC_OK:
            return true;
         case WebdavStatus.SC_NOT_FOUND:
            return false;
         default:
            HttpException ex = new HttpException();
            ex.setReasonCode(status);
            ex.setReason(head.getStatusText());
            throw ex;
      }
   }
   
   public static boolean collectionExists(HttpClient client, HttpURL httpURL)
      throws IOException, HttpException
   {
      Vector props = new Vector(1);
      props.add(RESOURCETYPE);
      PropFindMethod propFind = new PropFindMethod(httpURL.getEscapedURI(), 
                                                    0, PropFindMethod.BY_NAME);
      propFind.setFollowRedirects(true);
      propFind.setPropertyNames(props.elements());
      propFind.setAssertHrefsArePathes(true);
      propFind.setDecodeResponseHrefs("UTF-8");
      
      int status = client.executeMethod(propFind);
      switch (status) {
         case WebdavStatus.SC_MULTI_STATUS:
            Property p = findProperty(propFind, RESOURCETYPE, httpURL.getPath());
            if (p instanceof ResourceTypeProperty) {
               return ((ResourceTypeProperty)p).isCollection();
            } else {
               throw new WebdavException("PROPFFIND does not return resourcetype");
            }
         case WebdavStatus.SC_NOT_FOUND:
            return false;
         default:
            HttpException ex = new HttpException();
            ex.setReasonCode(status);
            ex.setReason(propFind.getStatusText());
            throw ex;
      }
   }
      
   public static long getLastModified(HttpClient client, HttpURL url)
      throws IOException, HttpException
   {
      Vector props = new Vector(1);
      props.add(GETLASTMODIFIED);
      PropFindMethod propFind = new PropFindMethod(url.getEscapedURI(), 0);
      propFind.setPropertyNames(props.elements());
      propFind.setFollowRedirects(true);
      propFind.setAssertHrefsArePathes(true);
      propFind.setDecodeResponseHrefs("UTF-8");
      
      int status = client.executeMethod(propFind);
      switch (status) {
         case WebdavStatus.SC_MULTI_STATUS:
            Property p = findProperty(propFind, GETLASTMODIFIED, url.getPath());
            if (p != null) {
               try {
                  Date d = GETLASTMODIFIED_FORMAT.parse(p.getPropertyAsString());
                  return d.getTime();
               } 
               catch (ParseException e) {
                  throw new HttpException("Invalid lastmodified property: " +
                        p.getPropertyAsString());
               }
            } 
            throw new HttpException("PROPFIND does not return lastmodified.");
         default:
            HttpException ex = new HttpException();
            ex.setReasonCode(status);
            ex.setReason(propFind.getStatusText());
            throw ex;
      }
   }
   
   /**
    * 
    * @param client
    * @param httpURL
    * @param lockToken the locktoken to be used or <code>null</code> if 
    *         none is to be used
    * @throws IOException
    * @throws HttpException
    */
   public static boolean assureExistingCollection(HttpClient client, 
                                                  HttpURL httpURL,
                                                  String lockToken)
      throws IOException, HttpException
   {
      String path = httpURL.getPath();
      if (!path.endsWith("/")) {
         path = path + "/";
      }
      Stack toBeCreated = new Stack();
      
      while (!path.equals("/")) {
         HttpURL parent = Utils.createHttpURL(httpURL, path);
         if (!collectionExists(client, parent)) {
            toBeCreated.push(path);
            path = path.substring(0, path.lastIndexOf("/", path.length()-2)+1);
         } else {
            break;
         }
      }

      boolean created = !toBeCreated.empty(); 
      while(!toBeCreated.empty()) {
         HttpURL newColl = Utils.createHttpURL(httpURL, (String)toBeCreated.pop());
         MkcolMethod mkcol = new MkcolMethod(newColl.getEscapedURI());
         mkcol.setFollowRedirects(true);
         generateIfHeader(mkcol, lockToken);
         int status = client.executeMethod(mkcol);
         if (status != WebdavStatus.SC_CREATED) {
            HttpException ex = new HttpException("Can't create collection " + 
                                                 newColl);
            ex.setReasonCode(status);
            ex.setReason(mkcol.getStatusText());
            throw ex;
         }
      }
      return created;
   }
   
   public static void putFile(HttpClient client, 
                              HttpURL url, 
                              InputStream is,
                              String contentType,
                              String lockToken)
      throws IOException, HttpException
   {
      PutMethod put = new PutMethod(url.getEscapedURI());
      generateIfHeader(put, lockToken);
      put.setRequestHeader("Content-Type", contentType);
      put.setRequestBody(is);
      put.setFollowRedirects(true);
      int status = client.executeMethod(put);
      switch (status) {
         case WebdavStatus.SC_OK:
         case WebdavStatus.SC_CREATED:
         case WebdavStatus.SC_NO_CONTENT:
            return;
         default:
            HttpException ex = new HttpException();
            ex.setReason(put.getStatusText());
            ex.setReasonCode(status);
            throw ex;
      }
   }
   
   public static InputStream getFile(HttpClient client, HttpURL url)
      throws IOException, HttpException
   {
      GetMethod get = new GetMethod(url.toString());
      get.setFollowRedirects(true);
      int status = client.executeMethod(get);
      
      switch (status) {
         case WebdavStatus.SC_OK:
            return get.getResponseBodyAsStream();
         default:
            HttpException ex = new HttpException();
            ex.setReason(get.getStatusText());
            ex.setReasonCode(status);
            throw ex;
      }
      
   }
   
   public static void generateIfHeader(HttpMethod method, String lockToken) {
      if (lockToken != null) {
         Header ifHeader = new Header();
         ifHeader.setName("If");
         ifHeader.setValue("(<" + lockToken + ">)");
         method.addRequestHeader(ifHeader);
      }
   }

   public static String lockResource(HttpClient client, HttpURL url, 
                                     String ownerInfo, int depth, int timeout)
      throws IOException, HttpException
   {
      LockMethod lock = new LockMethod(url.getEscapedURI());
      lock.setDepth(depth);
      lock.setTimeout(timeout);
      lock.setOwner(ownerInfo);
      //lock.setDebug(1);
      lock.setFollowRedirects(true);
      int status = client.executeMethod(lock);
      if (status == WebdavStatus.SC_OK) {
         Header header = lock.getResponseHeader("Lock-Token");
         if (header != null) {
            String l = header.getValue();
            return l.substring(1, l.length()-1);
         } else {
            String l = lock.getLockToken();
            if (l != null) {
               return l;
            }
            throw new WebdavException("LOCK does not provide a lock token.");
         }
      } else if (status == WebdavStatus.SC_MULTI_STATUS) {
         throw Utils.makeBuildException("Can't lock", lock.getResponses());
      } else {
         throw Utils.makeBuildException("Can't lock", status, lock.getStatusText());
      }
   }
   
   public static void unlockResource(HttpClient client, HttpURL url, 
                                     String lockToken)
      throws IOException, HttpException
   {
      UnlockMethod unlock = new UnlockMethod(url.getEscapedURI(), lockToken);
      unlock.setFollowRedirects(true);
      int status = client.executeMethod(unlock);
      
      switch (status) {
         case WebdavStatus.SC_OK:
         case WebdavStatus.SC_NO_CONTENT:
            return;
         
         default:
            HttpException ex = new HttpException();
            ex.setReasonCode(status);
            ex.setReason(unlock.getStatusText());
            throw ex;
      }
   }

   public static void copyResource(HttpClient client, HttpURL url, 
                                   String destination, int depth, boolean overwrite)
      throws IOException, HttpException 
   {
      CopyMethod copy = new CopyMethod(
              url.getEscapedURI(), 
              destination, 
              overwrite, 
              depth);
      copy.setFollowRedirects(true);
      int status = client.executeMethod(copy);
      switch (status) {
         case WebdavStatus.SC_OK:
         case WebdavStatus.SC_CREATED:
         case WebdavStatus.SC_NO_CONTENT:
             return;
         
         default:
             HttpException ex = new HttpException();
             ex.setReasonCode(status);
             ex.setReason(copy.getStatusText());
             throw ex;
      }
   }

   public static void moveResource(HttpClient client, HttpURL url, 
                                   String destination, boolean overwrite)
      throws IOException, HttpException 
   {
      MoveMethod move = new MoveMethod(url.getEscapedURI(), destination, overwrite);
      move.setFollowRedirects(true);
      int status = client.executeMethod(move);
      switch (status) {
         case WebdavStatus.SC_OK:
         case WebdavStatus.SC_CREATED:
         case WebdavStatus.SC_NO_CONTENT:
             return;

         default:
             HttpException ex = new HttpException();
             ex.setReasonCode(status);
             ex.setReason(move.getStatusText());
             throw ex;
   }
}

   public static BuildException makeBuildException(String msg, Exception e) {
      if (e instanceof HttpException) {
         HttpException he = (HttpException)e; 
         return new BuildException(
               msg + " " + e.getMessage() + " (" + 
               (he.getReason() != null 
                     ? he.getReason() 
                     : HttpStatus.getStatusText(he.getReasonCode())) + 
               ")");
         
      } else {
         return new BuildException(msg + " (" + e.toString() + ")", e);
      }
   }

   public static BuildException makeBuildException(String msg, int status) {
      return new BuildException(msg + " (" + 
                                HttpStatus.getStatusText(status) +
                                ")");
   }
   public static BuildException makeBuildException(String msg, 
         int status, String statusText) 
   {
      return new BuildException(msg + " (" + 
                                status + ", " + statusText + ")");
   }
   
   public static BuildException makeBuildException(
         String msg,
         Enumeration enumOfResponseEntities)
   {
      StringBuffer b = new StringBuffer();
      
      b.append(msg).append("\n");
      
      for(;enumOfResponseEntities.hasMoreElements();) {
         ResponseEntity e = (ResponseEntity)enumOfResponseEntities.nextElement();
         
         b.append(e.getHref())
          .append(" ")
          .append(HttpStatus.getStatusText(e.getStatusCode()))
          .append("\n");
      }
      
      return new BuildException(b.toString());
   }
   
   
   public static HttpURL createHttpURL(HttpURL base, String relative) 
       throws URIException
   {
     if (base instanceof HttpsURL) {
        return new HttpsURL((HttpsURL)base, relative);
     } else {
        return new HttpURL(base, relative);
     }
   }
    
   public static HttpURL createHttpURL(String url) throws URIException
   {
      if (url.startsWith("https://")) {
         return new HttpsURL(url);
      } else {
         return new HttpURL(url);
      }
   }


}
