content: /*
   * Copyright (c) 2010 Pentaho Corporation.  All rights reserved. 
   * This software was developed by Pentaho Corporation and is provided under the terms 
   * of the GNU Lesser General Public License, Version 2.1. You may not use 
   * this file except in compliance with the license. If you need a copy of the license, 
   * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Pentaho 
   * Package Manager.  The Initial Developer is Pentaho Corporation.
   *
   * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
   * the license for the specific language governing your rights and limitations.
   */
  
  /*
   *    PackageManager.java
   *    Copyright (c) 2009 Pentaho Corporation
   */
  
  package org.pentaho.packageManagement;
  
  import java.beans.Beans;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.PrintStream;
  import java.net.Authenticator;
  import java.net.InetSocketAddress;
  import java.net.PasswordAuthentication;
  import java.net.Proxy;
  import java.net.URL;
  import java.util.List;
  import java.util.Map;
  import java.util.Properties;
  
  /**
   * Abstract base class for package managers. Contains methods to manage the location
   * of the central package repository, the home directory for installing packages,
   * the name and version of the base software system and a http proxy.
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   * @version $Revision: 44030 $
   */
  public abstract class PackageManager {
    
    public static PackageManager create() {
      PackageManager pm = new DefaultPackageManager();
      pm.establishProxy();
      
      try {
        // See if org.pentaho.packageManagement.manager has been set
        String managerName = System.getProperty("org.pentaho.packageManagement.manager");
        if (managerName != null && managerName.length() > 0) {
          Object manager = Beans.instantiate(pm.getClass().getClassLoader(), managerName);
          if (manager instanceof PackageManager) {
            pm = (PackageManager)manager;
          }
        } else {
  
          // See if there is a named package manager specified in $HOME/PackageManager.props
          // that we should try to instantiate
          File packageManagerPropsFile = new File(System.getProperty("user.home") 
              + File.separator + "PackageManager.props");
          if (packageManagerPropsFile.exists()) {
            Properties pmProps = new Properties();
            pmProps.load(new FileInputStream(packageManagerPropsFile));
            managerName = pmProps.getProperty("org.pentaho.packageManager.manager");
            if (managerName != null && managerName.length() > 0) {
              Object manager = Beans.instantiate(pm.getClass().getClassLoader(), managerName);
              if (manager instanceof PackageManager) {
                pm = (PackageManager)manager;
              }
            }
          }
        }
      } catch (Exception ex) {
        // ignore any problems and just return the default package manager
        System.err.println("Problem instantiating package manager. Using DefaultPackageManager.");
      }
      return pm;
    }
    
    /** The local directory for storing the user's installed packages */
    protected File m_packageHome;
    
    /** The URL to the global package meta data repository */
    protected URL m_packageRepository;
    
    /** The name of the base system being managed by this package manager */
    protected String m_baseSystemName;
    
    /** The version of the base system being managed by this package manager */
    protected Object m_baseSystemVersion;
    
    /** Proxy for http connections */
    protected Proxy m_httpProxy;
    
    /** The user name for the proxy */
    protected String m_proxyUsername;
    
    /** The password for the proxy */
    protected String m_proxyPassword;
    
    /**
     * Tries to configure a Proxy object for use in an Authenticator
     * if there is a proxy defined by the properties http.proxyHost
     * and http.proxyPort, and if the user has set values for the
     * properties (note, these are not standard java properties)
     * http.proxyUser and http.proxyPassword.
     * 
     */
    public void establishProxy() {
      // check for proxy properties
      String proxyHost = System.getProperty("http.proxyHost");
      String proxyPort = System.getProperty("http.proxyPort");
      if (proxyHost != null && proxyHost.length() > 0) {
        int portNum = 80;
        if (proxyPort != null && proxyPort.length() > 0) {
          portNum = Integer.parseInt(proxyPort);
        }
        InetSocketAddress sa = new InetSocketAddress(proxyHost, portNum);
        setProxy(new Proxy(Proxy.Type.HTTP, sa));
      }
      
      // check for authentication
      String proxyUserName = System.getProperty("http.proxyUser");
      String proxyPassword = System.getProperty("http.proxyPassword");
      if (proxyUserName != null && proxyUserName.length() > 0 &&
          proxyPassword != null && proxyPassword.length() > 0) {
        
        setProxyUsername(proxyUserName);
        setProxyPassword(proxyPassword);
      }
    }
    
    /**
     * Sets an new default Authenticator that will return the values
     * set through setProxyUsername() and setProxyPassword.
     * 
     * @return true if the Authenticator was set successfully.
     */
    public boolean setProxyAuthentication() {
      if (m_httpProxy != null && m_proxyUsername != null && 
          m_proxyPassword != null) {
        Authenticator.setDefault(new Authenticator() {
          protected PasswordAuthentication getPasswordAuthentication() {
            return new
               PasswordAuthentication(m_proxyUsername,m_proxyPassword.toCharArray());
        }});
        return true;
      }
      
      return false;
    }
    
    /**
     * Set the location (directory) of installed packages.
     * 
     * @param packageHome the file system location of installed packages.
     */
    public void setPackageHome(File packageHome) {
      m_packageHome = packageHome;
    }
    
    /**
     * Get the location (directory) of installed packages
     * 
     * @return the directory containing installed packages.
     */
    public File getPackageHome() {
      return m_packageHome;
    }
    
    /**
     * Set the name of the main software system for which we manage
     * packages.
     * 
     * @param baseS the name of the base software system
     */
    public void setBaseSystemName(String baseS) {
      m_baseSystemName = baseS;
    }
    
    /**
     * Get the name of the main software system for which we manage
     * packages.
     * 
     * @return the name of the base software system.
     */
    public String getBaseSystemName() {
      return m_baseSystemName;
    }
    
    /**
     * Set the current version of the base system for which we manage
     * packages. 
     * 
     * @param systemV the current version of the main software system.
     */
    public void setBaseSystemVersion(Object systemV) {
      m_baseSystemVersion = systemV;
    }
    
    /**
     * Get the current installed version of the main system for
     * which we manage packages.
     * 
     * @return the installed version of the base system.
     */
    public Object getBaseSystemVersion() {
      return m_baseSystemVersion;
    }
    
    /**
     * Set the URL to the repository of package meta data.
     * 
     * @param repositoryURL the URL to the repository of package meta data.
     */
    public void setPackageRepositoryURL(URL repositoryURL) {
      m_packageRepository = repositoryURL;
    }
    
    /**
     * Get the URL to the repository of package meta data.
     * 
     * @return the URL to the repository of package meta data.
     */
    public URL getPackageRepositoryURL() {
      return m_packageRepository;
    }
    
    /**
     * Set a proxy to use for accessing the internet (default is no proxy).
     * 
     * @param proxyToUse a proxy to use.
     */
    public void setProxy(Proxy proxyToUse) {
      m_httpProxy = proxyToUse;
    }
    
    /**
     * Get the proxy in use.
     * 
     * @return the proxy in use or null if no proxy is being used.
     */
    public Proxy getProxy() {
      return m_httpProxy;
    }
    
    /**
     * Set the user name for authentication with the proxy.
     * 
     * @param proxyUsername the user name to use for proxy authentication.
     */
    public void setProxyUsername(String proxyUsername) {
      m_proxyUsername = proxyUsername;
    }
    
    /**
     * Set the password for authentication with the proxy.
     * 
     * @param proxyPassword the password to use for proxy authentication.
     */
    public void setProxyPassword(String proxyPassword) {
      m_proxyPassword = proxyPassword;
    }
    
    /**
     * Gets an array of bytes containing a zip of all the repository
     * meta data and supporting files. Does *not* contain any package
     * archives etc., only a snapshot of the meta data. Could be used
     * by clients to establish a cache of meta data.
     * 
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to. 
     * @return a zip compressed array of bytes.
     * @throws Exception if the repository meta data can't be returned as a zip
     */
    public abstract byte[] getRepositoryPackageMetaDataOnlyAsZip(PrintStream... progress)
      throws Exception;
    
    /**
     * Get package information from the supplied package archive file.
     * 
     * @param packageArchivePath the path to the package archive file
     * @return a Package object encapsulating the package meta data.
     * @throws Exception if the package meta data can't be retrieved.
     */
    public abstract Package getPackageArchiveInfo(String packageArchivePath) throws Exception;
    
    /**
     * Get package information on the named installed package.
     * 
     * @param packageName the name of the package to get information about.
     * @return a Package object encapsulating the package meta data or null if the
     * package is not installed.
     * 
     * @throws Exception if the package meta data can't be retrieved.
     */
    public abstract Package getInstalledPackageInfo(String packageName) throws Exception;
    
    // public abstract Map getPackageArchiveInfo(URL packageURL);
    
    /**
     * Get package information on the named package from the repository. If multiple
     * versions of the package are available, it assumes that the most recent is
     * required.
     * 
     * @param packageName the name of the package to get information about.
     * @return a Package object encapsulating the package meta data.
     * @throws Exception if the package meta data can't be retrieved.
     */
    public abstract Package getRepositoryPackageInfo(String packageName) throws Exception;
    
    /**
     * Get package information on the named package from the repository.
     * 
     * @param packageName the name of the package to get information about.
     * @param version the version of the package to retrieve (may be null if not applicable).
     * @return a Package object encapsulating the package meta data.
     * @throws Exception if the package meta data can't be retrieved.
     */
    public abstract Package getRepositoryPackageInfo(String packageName, Object version) throws Exception;
    
    /**
     * Get a list of available versions of the named package.
     * 
     * @param packageName the name of the package to get versions.
     * @return a list of available versions (or null if not applicable)
     * @throws Exception if something goes wrong while trying to retrieve the list
     * of versions.
     */
    public abstract List<Object> getRepositoryPackageVersions(String packageName) throws Exception;
    
    /**
     * Get package information on the package at the given URL.
     * 
     * @param packageURL the URL to the package.
     * @return a Package object encapsulating the package meta data
     * @throws Exception if the package meta data can't be retrieved.
     */
    public abstract Package getURLPackageInfo(URL packageURL) throws Exception;
    
    /**
     * Install a package from an archive on the local file system.
     * 
     * @param packageArchivePath the path to the package archive file.
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to.
     * @return the name of the package installed
     * @throws Exception if the package can't be installed for some reason.
     */
    public abstract String installPackageFromArchive(String packageArchivePath, 
        PrintStream... progress) throws Exception;
    
    /**
     * Install a package sourced from the repository.
     * 
     * @param packageName the name of the package to install
     * @param version the version of the package to install (may be null if not applicable).
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to.
     * @throws Exception if the package can't be installed for some reason.
     */
    public abstract void installPackageFromRepository(String packageName, 
        Object version, PrintStream... progress) throws Exception;
    
    /**
     * Install a package sourced from a given URL.
     * 
     * @param packageURL the URL to the package.
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to.
     * @return the name of the package installed
     * @throws Exception if the package can't be installed for some reason.
     */
    public abstract String installPackageFromURL(URL packageURL, 
        PrintStream... progress) throws Exception;
    
    /**
     * Installs all the packages in the supplied list.
     * 
     * @param toInstall a list of Packages to install.
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to.
     * @throws Exception if something goes wrong during the installation process.
     */
    public abstract void installPackages(List<Package> toInstall, 
        PrintStream... progress) throws Exception;
    
    /**
     * Uninstall a package.
     * 
     * @param packageName the package to uninstall.
     * @param progress optional varargs parameter, that, if supplied, is expected to contain one
     * or more PrintStream objects to write progress to.
     * @throws Exception if the named package could not be removed for some reason.
     */
    public abstract void uninstallPackage(String packageName, 
        PrintStream... progress) throws Exception;
    
    /**
     * Get a list of installed packages.
     * 
     * @return a list of installed packages.
     * @throws Exception if a list of packages can't be determined.
     */
    public abstract List<Package> getInstalledPackages() throws Exception;
    
    /**
     * Get all packages that the system knows about (i.e. all packages
     * contained in the repository).
     * 
     * @param progress optional varargs parameter, that, if supplied is
     * expected to contain one or more PrintStream objects to write
     * progress to.
     * @return a list of all packages.
     * @throws Exception if a list of packages can't be determined.
     */
    public abstract List<Package> getAllPackages(PrintStream... progress) 
      throws Exception;
    
    /**
     * Get a list of packages that are not currently installed.
     * 
     * @return a list of packages that are not currently installed.
     * @throws Exception if a list of packages can't be determined.
     */
    public abstract List<Package> getAvailablePackages() throws Exception;
    
    /**
     * Gets a full list of packages (encapsulated in Dependency
     * objects) that are required by directly and indirectly by the
     * named target package. Also builds a Map of any packages that
     * are required by more than one package and where there is
     * a conflict of some sort (e.g. multiple conflicting versions).
     *  The keys of this map are package names
     * (strings), and each associated value is a list of Dependency 
     * objects.
     * 
     * @param target the package for which a list of dependencies is
     * required.
     * @param conflicts will hold any conflicts that are discovered while
     * building the full dependency list.
     * @return a list of packages that are directly and indirectly required
     * by the named target package.
     * @throws Exception if a problem occurs while building the dependency
     * list.
     */
    public abstract List<Dependency> 
      getAllDependenciesForPackage(Package target, 
          Map<String, List<Dependency>> conflicts) throws Exception;
  }
  