/**
   * Uploads a media file to the Joomla instance.
   *
   * @param subfolder Subfolder to upload the file
   * @param filename Name of the file to upload
   * @param filedata Data contained in the file
   * @return Relative URL of the uploaded file
   * @throws JoomlaException If the file could not be uploaded
   */
  public String uploadMediaFile(String subfolder, String filename, byte[] filedata)
      throws JoomlaException {
    try {
      LOG.log(Level.INFO, "Uploading {0}", new Object[] {filename});
      TimingOutCallback callback = new TimingOutCallback((getTimeout() + 120) * 1000);
      XmlRpcClient client = getXmlRpcClient();

      Object[] params = new Object[] {username, password, subfolder, filename, filedata};

      client.executeAsync(XMLRPC_METHOD_NEW_MEDIA, params, callback);

      String location = (String) callback.waitForResponse();

      LOG.log(Level.INFO, "Media file #{0} uploaded to {1}", new Object[] {filename, location});
      return location;
    } catch (TimeoutException ex) {
      throw new JoomlaException(ex);
    } catch (XmlRpcException ex) {
      throw new JoomlaException(ex);
    } catch (IOException ex) {
      throw new JoomlaException(ex);
    } catch (Throwable t) {
      throw new JoomlaException(t);
    }
  }
  /**
   * Gets a list of available content categories.
   *
   * @return List of categories found on the Joomla instance
   * @throws JoomlaException If the categories could not be obtained due to a connection error
   */
  public Map<Integer, String> listCategories() throws JoomlaException {
    logger.log(
        Level.INFO, "Executing {0} at {1} ", new Object[] {XMLRPC_METHOD_LIST_CATEGORIES, url});
    Map<Integer, String> categories = new HashMap<Integer, String>();
    try {
      int callTimeout = getTimeout() * 1000;
      TimingOutCallback callback = new TimingOutCallback(callTimeout);
      XmlRpcClient client = getXmlRpcClient();
      Object[] params = new Object[] {username, password};

      client.executeAsync(XMLRPC_METHOD_LIST_CATEGORIES, params, callback);
      // Object[] cats = (Object[]) client.execute(XMLRPC_METHOD_LIST_CATEGORIES, params);

      logger.log(
          Level.INFO,
          "Calling {0} and waiting for response (Timeout: {1} seconds)",
          new Object[] {XMLRPC_METHOD_LIST_CATEGORIES, callTimeout});
      Object[] cats = (Object[]) callback.waitForResponse();
      logger.log(
          Level.INFO,
          "Got response {0} from {1}. {2} results",
          new Object[] {XMLRPC_METHOD_LIST_CATEGORIES, url, callTimeout, cats.length});

      for (Object objCat : cats) {
        HashMap<String, String> cat = (HashMap<String, String>) objCat;
        try {
          categories.put(Integer.valueOf(cat.get("id")), cat.get("title"));
        } catch (Exception ex) {
          logger.log(
              Level.WARNING,
              "Unknown value found as category id: {0}. Skipping category.",
              cat.get("id"));
        }
      }

      return categories;
    } catch (TimeoutException ex) {
      throw new JoomlaException(ex);
    } catch (MalformedURLException ex) {
      throw new JoomlaException(ex);
    } catch (XmlRpcException ex) {
      throw new JoomlaException(ex);
    } catch (IOException ex) {
      throw new JoomlaException(ex);
    } catch (Throwable t) {
      throw new JoomlaException(t);
    }
  }
  /**
   * Deletes an article from the Joomla installation.
   *
   * @param foreignId Unique identifier of the article outside of Joomla
   */
  public void deleteArticle(String foreignId) throws JoomlaException {
    LOG.log(Level.INFO, "Executing {0} at {1} ", new Object[] {XMLRPC_METHOD_DELETE_ARTICLE, url});
    try {
      TimingOutCallback callback = new TimingOutCallback(getTimeout() * 1000);
      XmlRpcClient client = getXmlRpcClient();
      Object[] params = new Object[] {username, password, foreignId};

      client.executeAsync(XMLRPC_METHOD_DELETE_ARTICLE, params, callback);
      // String status = (String) client.execute(XMLRPC_METHOD_DELETE_ARTICLE, params);

      String status = (String) callback.waitForResponse();
    } catch (TimeoutException ex) {
      throw new JoomlaException(ex);
    } catch (MalformedURLException ex) {
      throw new JoomlaException(ex);
    } catch (XmlRpcException ex) {
      throw new JoomlaException(ex);
    } catch (IOException ex) {
      throw new JoomlaException(ex);
    } catch (Throwable t) {
      throw new JoomlaException(t);
    }
  }
  /**
   * Determines if the connection can communicate with the configured Joomla instance.
   *
   * @throws JoomlaException Exception thrown if a valid connection could not be established
   */
  public void validateConnection() throws JoomlaException {
    try {
      TimingOutCallback callback = new TimingOutCallback(getTimeout() * 1000);
      XmlRpcClient client = getXmlRpcClient();
      Object[] params = new Object[] {};
      client.executeAsync(XMLRPC_METHOD_VERSION, params, callback);
      String version = (String) callback.waitForResponse();

      if (version == null) {
        throw new InvalidResponseException();
      } else {
        if (version.startsWith(COMPATIBILITY)) {
          return;
        } else {
          throw new IncompatibleJoomlaPluginException(version);
        }
      }
    } catch (TimeoutException e) {
      throw new JoomlaTimeoutException(e);
    } catch (Throwable t) {
      throw new InvalidResponseException(t);
    }
  }
  /**
   * Uploads a new article to the configured Joomla instance.
   *
   * @param foreignId Unique identifier of the article outside of Joomla
   * @param title Title of the article
   * @param intro Article introduction
   * @param story Full story
   * @param author Author(s) of the article
   * @param categoryId Category in which to post the article
   * @param frontPage Should the article appear on the front page
   * @param displayOrder Order in which to display the article
   * @param keywords Keywords of the article (separated with commas)
   * @param description Meta description of the article
   * @param publish Date and time when the article should be posted. If {@code null} the article
   *     will be published immediately
   * @param expire Date and time when the article should expire. If {@code null} the article will
   *     not have an expiration date
   * @return Unique identifier of the article in Joomla
   * @throws JoomlaException
   */
  public Integer newArticle(
      String foreignId,
      String title,
      String intro,
      String story,
      String author,
      String categoryId,
      boolean frontPage,
      String displayOrder,
      String keywords,
      String description,
      Date publish,
      Date expire)
      throws JoomlaException {
    LOG.log(Level.INFO, "Executing {0} at {1} ", new Object[] {XMLRPC_METHOD_NEW_ARTICLE, url});
    try {
      TimingOutCallback callback = new TimingOutCallback(getTimeout() * 1000);
      XmlRpcClient client = getXmlRpcClient();

      String showOnFrontPage = (frontPage ? "true" : "false");
      Object publishTime = "0";
      Object expireTime = "0";

      if (publish != null) {
        publishTime = publish;
      }

      if (expire != null) {
        expireTime = expire;
      }

      Object[] params =
          new Object[] {
            username,
            password,
            foreignId,
            title,
            intro,
            story,
            author,
            categoryId,
            showOnFrontPage,
            displayOrder,
            keywords,
            description,
            publishTime,
            expireTime
          };

      client.executeAsync(XMLRPC_METHOD_NEW_ARTICLE, params, callback);

      String articleId = (String) callback.waitForResponse();

      if (articleId != null && !articleId.trim().isEmpty()) {
        return Integer.valueOf(articleId);
      } else {
        throw new JoomlaException(
            "Article was not uploaded to Joomla. Joomla ID was not received from XML-RPC service");
      }
    } catch (TimeoutException ex) {
      throw new JoomlaException(ex);
    } catch (MalformedURLException ex) {
      throw new JoomlaException(ex);
    } catch (XmlRpcException ex) {
      throw new JoomlaException(ex);
    } catch (IOException ex) {
      throw new JoomlaException(ex);
    } catch (Throwable t) {
      throw new JoomlaException(t);
    }
  }