AbstractDbDataDao.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.openr66.database.data;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.waarp.common.database.DbPreparedStatement;
import org.waarp.common.database.data.AbstractDbData;
import org.waarp.common.database.data.DbValue;
import org.waarp.common.database.exception.WaarpDatabaseException;
import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
import org.waarp.common.database.exception.WaarpDatabaseNoDataException;
import org.waarp.common.database.exception.WaarpDatabaseSqlException;
import org.waarp.common.database.model.DbModelAbstract;
import org.waarp.common.json.JsonHandler;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.openr66.dao.AbstractDAO;
import org.waarp.openr66.dao.DAOFactory;
import org.waarp.openr66.dao.exception.DAOConnectionException;
import org.waarp.openr66.dao.exception.DAONoDataException;

import java.sql.Types;
import java.util.Iterator;
import java.util.Map.Entry;

/**
 * Abstract database table implementation
 */
public abstract class AbstractDbDataDao<E> extends AbstractDbData {
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(AbstractDbDataDao.class);
  public static final String JSON_MODEL = "@model";
  protected static final String SHOULD_NOT_BE_CALLED = "Should not be called";

  protected E pojo;

  /**
   * Abstract constructor
   */
  protected AbstractDbDataDao() {
    // nothing
  }

  /**
   * Validate Byte array max length
   *
   * @param values the values to check against Types.VARBINARY
   *
   * @throws WaarpDatabaseSqlException if length is not acceptable
   */
  public static void validateLength(final byte[]... values)
      throws WaarpDatabaseSqlException {
    for (final byte[] value : values) {
      if (value != null && value.length > DbModelAbstract.MAX_BINARY * 2) {
        throw new WaarpDatabaseSqlException(
            "BINARY value exceed max size: " + value.length + " (" +
            DbModelAbstract.MAX_BINARY + ")");
      }
    }
  }

  /**
   * Validate String max length
   *
   * @param type between Types.VARCHAR, NVARCHAR, LONGVARCHAR
   * @param values the values to check against same type
   *
   * @throws WaarpDatabaseSqlException if length is not acceptable
   */
  public static void validateLength(final int type, final String... values)
      throws WaarpDatabaseSqlException {
    for (final String value : values) {
      if (value == null) {
        continue;
      }
      switch (type) {
        case Types.VARCHAR:
          if (value.length() > DbModelAbstract.MAX_VARCHAR) {
            throw new WaarpDatabaseSqlException(
                "VARCHAR value exceed max size: " + value.length() + " (" +
                DbModelAbstract.MAX_VARCHAR + ")");
          }
          break;
        case Types.NVARCHAR:
          if (value.length() > DbModelAbstract.MAX_KEY_VARCHAR) {
            throw new WaarpDatabaseSqlException(
                "VARCHAR as KEY value exceed max size: " + value.length() +
                " (" + DbModelAbstract.MAX_KEY_VARCHAR + ")");
          }
          break;
        case Types.LONGVARCHAR:
          if (value.length() > DbModelAbstract.MAX_LONGVARCHAR) {
            throw new WaarpDatabaseSqlException(
                "LONGVARCHAR value exceed max size: " + value.length() + " (" +
                DbModelAbstract.MAX_LONGVARCHAR + ")");
          }
          break;
        default:
          break;
      }
    }
  }

  protected abstract String getTable();

  protected abstract void checkValues() throws WaarpDatabaseSqlException;

  protected abstract AbstractDAO<E> getDao(final boolean isCacheable)
      throws DAOConnectionException;

  protected abstract String getPrimaryKey();

  protected abstract String getPrimaryField();

  /**
   * Change UpdatedInfo status
   *
   * @param info
   */
  public abstract void changeUpdatedInfo(AbstractDbData.UpdatedInfo info);

  /**
   * Test the existence of the current object
   *
   * @return True if the object exists
   *
   * @throws WaarpDatabaseException
   */
  @Override
  public boolean exist() throws WaarpDatabaseException {
    AbstractDAO<E> abstractDAO = null;
    try {
      abstractDAO = getDao(true);
      return abstractDAO.exist(getPrimaryKey());
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } finally {
      DAOFactory.closeDAO(abstractDAO);
    }
  }

  /**
   * Select object from table
   *
   * @throws WaarpDatabaseException
   */
  @Override
  public void select() throws WaarpDatabaseException {
    AbstractDAO<E> abstractDAO = null;
    try {
      abstractDAO = getDao(true);
      pojo = abstractDAO.select(getPrimaryKey());
      isSaved = true;
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } catch (final DAONoDataException e) {
      throw new WaarpDatabaseNoDataException((e));
    } finally {
      DAOFactory.closeDAO(abstractDAO);
    }
  }

  /**
   * Insert object into table
   *
   * @throws WaarpDatabaseException
   */
  @Override
  public void insert() throws WaarpDatabaseException {
    if (isSaved) {
      return;
    }
    checkValues();
    AbstractDAO<E> abstractDAO = null;
    try {
      abstractDAO = getDao(false);
      abstractDAO.insert(pojo);
      isSaved = true;
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } finally {
      DAOFactory.closeDAO(abstractDAO);
    }
  }

  /**
   * Update object to table
   *
   * @throws WaarpDatabaseException
   */
  @Override
  public void update() throws WaarpDatabaseException {
    if (isSaved) {
      return;
    }
    checkValues();
    AbstractDAO<E> abstractDAO = null;
    try {
      abstractDAO = getDao(false);
      abstractDAO.update(pojo);
      isSaved = true;
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } catch (final DAONoDataException e) {
      throw new WaarpDatabaseNoDataException((e));
    } finally {
      DAOFactory.closeDAO(abstractDAO);
    }
  }

  /**
   * Delete object from table
   *
   * @throws WaarpDatabaseException
   */
  @Override
  public void delete() throws WaarpDatabaseException {
    AbstractDAO<E> abstractDAO = null;
    try {
      abstractDAO = getDao(false);
      abstractDAO.delete(pojo);
      isSaved = false;
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } catch (final DAONoDataException e) {
      throw new WaarpDatabaseNoDataException((e));
    } finally {
      DAOFactory.closeDAO(abstractDAO);
    }
  }

  /**
   * @return the PoJo as Json
   */
  @Override
  public final String asJson() {
    final ObjectNode node = getJson();
    return JsonHandler.writeAsString(node);
  }

  /**
   * @return the PoJo as Json (shall not be used except for native Json but
   *     possibly incorrect
   */
  public String toJson() {
    return JsonHandler.writeAsString(pojo);
  }

  /**
   * Create the equivalent object in Json (no database access)
   *
   * @return The ObjectNode Json equivalent
   */
  @Override
  public ObjectNode getJson() {
    final ObjectNode node = JsonHandler.createObjectNode();
    node.put(JSON_MODEL, getClass().getSimpleName());
    final String json = JsonHandler.writeAsString(pojo);
    final ObjectNode subnode = JsonHandler.getFromString(json);
    if (subnode != null) {
      for (final Iterator<Entry<String, JsonNode>> it = subnode.fields();
           it.hasNext(); ) {
        final Entry<String, JsonNode> entry = it.next();
        node.set(entry.getKey(), entry.getValue());
      }
    }
    return node;
  }

  protected abstract void setFromJson(String field, JsonNode value)
      throws WaarpDatabaseSqlException;

  /**
   * Set the values from the Json node to the current object (no database
   * access)
   *
   * @param node
   * @param ignorePrimaryKey True will ignore primaryKey from Json
   *
   * @throws WaarpDatabaseSqlException
   */
  @Override
  public void setFromJson(final ObjectNode node, final boolean ignorePrimaryKey)
      throws WaarpDatabaseSqlException {
    boolean foundPrimaryKey = false;
    for (final Iterator<Entry<String, JsonNode>> it = node.fields();
         it.hasNext(); ) {
      final Entry<String, JsonNode> entry = it.next();
      logger.debug("{} = {}", entry.getKey(), entry.getValue());
      if ("UPDATEDINFO".equalsIgnoreCase(entry.getKey())) {
        continue;
      }
      if (getPrimaryField().equalsIgnoreCase(entry.getKey())) {
        if (ignorePrimaryKey) {
          continue;
        } else {
          foundPrimaryKey = !entry.getValue().isNull() &&
                            !entry.getValue().asText().isEmpty();
        }
      }
      setFromJson(entry.getKey(), entry.getValue());
      isSaved = false;
    }
    checkValues();
    if (!ignorePrimaryKey && foundPrimaryKey) {
      try {
        insert();
      } catch (final WaarpDatabaseException e) {
        try {
          update();
        } catch (final WaarpDatabaseException ex) {
          logger.error("Cannot save item: {}", ex.getMessage());
        }
      }
    }
  }

  @Override
  protected void initObject() {
    // nothing
  }

  /**
   * {@link UnsupportedOperationException}
   *
   * @return never
   */
  @Override
  protected String getWherePrimaryKey() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   */
  @Override
  protected void setPrimaryKey() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   *
   * @return never
   */
  @Override
  protected String getSelectAllFields() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   *
   * @return never
   */
  @Override
  protected String getInsertAllValues() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   *
   * @return never
   */
  @Override
  protected String getUpdateAllFields() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   */
  @Override
  protected void setToArray() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   */
  @Override
  protected void setFromArray() {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }

  /**
   * {@link UnsupportedOperationException}
   */
  @Override
  protected void getValues(final DbPreparedStatement preparedStatement,
                           final DbValue[] values) {
    throw new UnsupportedOperationException(SHOULD_NOT_BE_CALLED);
  }
}