/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.hadoop.hive.metastore.messaging;

import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.metastore.api.Database;
import org.apache.hadoop.hive.metastore.api.Function;
import org.apache.hadoop.hive.metastore.api.Index;
import org.apache.hadoop.hive.metastore.api.Partition;
import org.apache.hadoop.hive.metastore.api.Table;
import org.apache.hadoop.util.ReflectionUtils;

import java.util.Iterator;
import java.util.Map;

/**
 * Abstract Factory for the construction of HCatalog message instances.
 */
public abstract class MessageFactory {

  // Common name constants for event messages
  public static final String ADD_PARTITION_EVENT = "ADD_PARTITION";
  public static final String ALTER_PARTITION_EVENT = "ALTER_PARTITION";
  public static final String DROP_PARTITION_EVENT = "DROP_PARTITION";
  public static final String CREATE_TABLE_EVENT = "CREATE_TABLE";
  public static final String ALTER_TABLE_EVENT = "ALTER_TABLE";
  public static final String DROP_TABLE_EVENT = "DROP_TABLE";
  public static final String CREATE_DATABASE_EVENT = "CREATE_DATABASE";
  public static final String DROP_DATABASE_EVENT = "DROP_DATABASE";
  public static final String INSERT_EVENT = "INSERT";
  public static final String CREATE_FUNCTION_EVENT = "CREATE_FUNCTION";
  public static final String DROP_FUNCTION_EVENT = "DROP_FUNCTION";
  public static final String CREATE_INDEX_EVENT = "CREATE_INDEX";
  public static final String DROP_INDEX_EVENT = "DROP_INDEX";
  public static final String ALTER_INDEX_EVENT = "ALTER_INDEX";


  private static MessageFactory instance = null;

  protected static final HiveConf hiveConf = new HiveConf();
  static {
    hiveConf.addResource("hive-site.xml");
  }

  // This parameter is retained for legacy reasons, in case someone implemented custom
  // factories. This, however, should not be the case, since this api was intended to
  // be internal-only, and we should manage the jms and json implementations without
  // needing this parameter. Marking as deprecated, for removal by 2.4 - see corresponding
  // note on the getDeserializer(String,String) method
  @Deprecated
  private static final String CONF_LABEL_HCAT_MESSAGE_FACTORY_IMPL_PREFIX = "hcatalog.message.factory.impl.";

  protected static final String MS_SERVER_URL = hiveConf.get(HiveConf.ConfVars.METASTOREURIS.name(), "");
  protected static final String MS_SERVICE_PRINCIPAL = hiveConf.get(HiveConf.ConfVars.METASTORE_KERBEROS_PRINCIPAL.name(), "");

  /**
   * Getter for MessageFactory instance.
   */
  public static MessageFactory getInstance() {
    if (instance == null) {
      instance =
          getInstance(hiveConf.get(HiveConf.ConfVars.METASTORE_EVENT_MESSAGE_FACTORY.varname));
    }
    return instance;
  }

  private static MessageFactory getInstance(String className) {
    try {
      return (MessageFactory)ReflectionUtils.newInstance(JavaUtils.loadClass(className), hiveConf);
    }
    catch (ClassNotFoundException classNotFound) {
      throw new IllegalStateException("Could not construct MessageFactory implementation: ", classNotFound);
    }
  }

  /**
   * Getter for MessageDeserializer, corresponding to the specified format and version.
   * @param format Serialization format for notifications.
   * @param version Version of serialization format (currently ignored.)
   * @return MessageDeserializer.
   */
  public static MessageDeserializer getDeserializer(String format,
                            String version) {
    return getInstance(hiveConf.get(CONF_LABEL_HCAT_MESSAGE_FACTORY_IMPL_PREFIX + format,
        HiveConf.ConfVars.METASTORE_EVENT_MESSAGE_FACTORY.varname)).getDeserializer();
    // Note : The reason this method exists outside the no-arg getDeserializer method is in
    // case there is a user-implemented MessageFactory that's used, and some the messages
    // are in an older format and the rest in another. Then, what MessageFactory is default
    // is irrelevant, we should always use the one that was used to create it to deserialize.
    //
    // There exist only 2 implementations of this - json and jms
    //
    // Additional note : rather than as a config parameter, does it make sense to have
    // this use jdbc-like semantics that each MessageFactory made available register
    // itself for discoverability? Might be worth pursuing.
  }

  public abstract MessageDeserializer getDeserializer();

  /**
   * Getter for message-format.
   */
  public abstract String getMessageFormat();

  /**
   * Factory method for CreateDatabaseMessage.
   * @param db The Database being added.
   * @return CreateDatabaseMessage instance.
   */
  public abstract CreateDatabaseMessage buildCreateDatabaseMessage(Database db);

  /**
   * Factory method for DropDatabaseMessage.
   * @param db The Database being dropped.
   * @return DropDatabaseMessage instance.
   */
  public abstract DropDatabaseMessage buildDropDatabaseMessage(Database db);

  /**
   * Factory method for CreateTableMessage.
   * @param table The Table being created.
   * @param files Iterator of files
   * @return CreateTableMessage instance.
   */
  public abstract CreateTableMessage buildCreateTableMessage(Table table, Iterator<String> files);

  /**
   * Factory method for AlterTableMessage.  Unlike most of these calls, this one can return null,
   * which means no message should be sent.  This is because there are many flavors of alter
   * table (add column, add partition, etc.).  Some are covered elsewhere (like add partition)
   * and some are not yet supported.
   * @param before The table before the alter
   * @param after The table after the alter
   * @return
   */
  public abstract AlterTableMessage buildAlterTableMessage(Table before, Table after);

  /**
   * Factory method for DropTableMessage.
   * @param table The Table being dropped.
   * @return DropTableMessage instance.
   */
  public abstract DropTableMessage buildDropTableMessage(Table table);

    /**
     * Factory method for AddPartitionMessage.
     * @param table The Table to which the partitions are added.
     * @param partitions The iterator to set of Partitions being added.
     * @param partitionFiles The iterator of partition files
     * @return AddPartitionMessage instance.
     */
  public abstract AddPartitionMessage buildAddPartitionMessage(Table table, Iterator<Partition> partitions,
      Iterator<PartitionFiles> partitionFiles);

  /**
   * Factory method for building AlterPartitionMessage
   * @param table The table in which the partition is being altered
   * @param before The partition before it was altered
   * @param after The partition after it was altered
   * @return a new AlterPartitionMessage
   */
  public abstract AlterPartitionMessage buildAlterPartitionMessage(Table table, Partition before,
                                                                   Partition after);

  /**
   * Factory method for DropPartitionMessage.
   * @param table The Table from which the partition is dropped.
   * @param partitions The set of partitions being dropped.
   * @return DropPartitionMessage instance.
   */
  public abstract DropPartitionMessage buildDropPartitionMessage(Table table, Iterator<Partition> partitions);

  /**
   * Factory method for CreateFunctionMessage.
   * @param fn The Function being added.
   * @return CreateFunctionMessage instance.
   */
  public abstract CreateFunctionMessage buildCreateFunctionMessage(Function fn);

  /**
   * Factory method for DropFunctionMessage.
   * @param fn The Function being dropped.
   * @return DropFunctionMessage instance.
   */
  public abstract DropFunctionMessage buildDropFunctionMessage(Function fn);

  /**
   * Factory method for CreateIndexMessage.
   * @param idx The Index being added.
   * @return CreateIndexMessage instance.
   */
  public abstract CreateIndexMessage buildCreateIndexMessage(Index idx);

  /**
   * Factory method for DropIndexMessage.
   * @param idx The Index being dropped.
   * @return DropIndexMessage instance.
   */
  public abstract DropIndexMessage buildDropIndexMessage(Index idx);

  /**
   * Factory method for AlterIndexMessage.
   * @param before The index before the alter
   * @param after The index after the alter
   * @return AlterIndexMessage
   */
  public abstract AlterIndexMessage buildAlterIndexMessage(Index before, Index after);

  /**
   * Factory method for building insert message
   *
   * @param db Name of the database the insert occurred in
   * @param table Name of the table the insert occurred in
   * @param partVals Partition values for the partition that the insert occurred in, may be null if
   *          the insert was done into a non-partitioned table
   * @param files Iterator of file created
   * @return instance of InsertMessage
   */
  public abstract InsertMessage buildInsertMessage(String db, String table,
      Map<String, String> partVals, Iterator<String> files);
}
