GenericDAO.java

package de.dlr.shepard.neo4Core.dao;

import de.dlr.shepard.neo4j.NeoConnector;
import de.dlr.shepard.util.Constants;
import de.dlr.shepard.util.CypherQueryHelper;
import de.dlr.shepard.util.CypherQueryHelper.Neighborhood;
import de.dlr.shepard.util.PaginationHelper;
import de.dlr.shepard.util.TraversalRules;
import io.quarkus.logging.Log;
import java.util.Collection;
import java.util.Map;
import org.neo4j.ogm.cypher.Filter;
import org.neo4j.ogm.cypher.query.Pagination;
import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.session.Session;

public abstract class GenericDAO<T> {

  protected static final int DEPTH_ENTITY = 1;

  protected Session session = null;

  protected GenericDAO() {
    session = NeoConnector.getInstance().getNeo4jSession();
  }

  /**
   * Find all instances of a certain entity T
   *
   * @return an Iterable over the found entities
   */
  public Collection<T> findAll() {
    Collection<T> iter = session.loadAll(getEntityType(), DEPTH_ENTITY);
    return iter;
  }

  /**
   * Find all instances of a certain entity T
   *
   * @param page which page should be fetched
   * @return an Iterable over the found entities
   */
  public Collection<T> findAll(PaginationHelper page) {
    Collection<T> iter = session.loadAll(getEntityType(), new Pagination(page.getPage(), page.getSize()), DEPTH_ENTITY);
    return iter;
  }

  /**
   * Find the entity with the given id
   *
   * @param id The given id
   * @return The entity with the given id or null
   */
  public T findByNeo4jId(long id) {
    return session.load(getEntityType(), id, DEPTH_ENTITY);
  }

  /**
   * Find the entity with the given id without any related entities
   *
   * @param id The given id
   * @return The entity with the given id or null
   */
  public T findLightByNeo4jId(long id) {
    return session.load(getEntityType(), id, 0);
  }

  /**
   * Find entities matching the given filter
   *
   * @param filter The given filter
   * @return An iterable with the found entities
   */
  public Collection<T> findMatching(Filter filter) {
    return session.loadAll(getEntityType(), filter, DEPTH_ENTITY);
  }

  /**
   * Delete an entity
   *
   * @param id The entity to be deleted
   * @return Whether the deletion was successful or not
   */
  public boolean deleteByNeo4jId(long id) {
    T entity = session.load(getEntityType(), id);
    if (entity != null) {
      session.delete(entity);
      return true;
    }
    return false;
  }

  /**
   * Save an entity and all related entities
   *
   * @param entity The entity to be saved
   * @return the saved entity
   */
  public T createOrUpdate(T entity) {
    session.save(entity, DEPTH_ENTITY);
    return entity;
  }

  /**
   * CAUTION: The query runs against the database and is not checked. You can do
   * anything you want.
   *
   * @param query     The query
   * @param paramsMap Map of parameters
   * @return Iterable The result
   */
  protected Iterable<T> findByQuery(String query, Map<String, Object> paramsMap) {
    Log.debugf("Run query: %s", query);
    StringBuilder str = new StringBuilder();
    for (var entry : paramsMap.entrySet()) {
      str.append("(" + entry.getKey() + ", " + entry.getValue() + "), ");
    }
    Log.debugf("queryParams: %s", str.toString());
    Iterable<T> iter = session.query(getEntityType(), query, paramsMap);
    return iter;
  }

  protected boolean runQuery(String query, Map<String, Object> paramsMap) {
    Log.debugf("Run query: %s", query);
    Result result = session.query(query, paramsMap);
    return result.queryStatistics().containsUpdates();
  }

  protected String getSearchForReachableReferencesByShepardIdQuery(
    TraversalRules traversalRule,
    long collectionShepardId,
    long startShepardId,
    String userName
  ) {
    String ret = "MATCH path = (col:Collection)-[:has_dataobject]->";
    ret += getTraversalRulesPath(traversalRule);
    ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
    ret += getWithPart("ns", "ret");
    ret +=
      " WHERE d." +
      Constants.SHEPARD_ID +
      " = " +
      startShepardId +
      " AND col." +
      Constants.SHEPARD_ID +
      " = " +
      collectionShepardId;
    ret += getReturnPart("ns", "ret", "col", userName);
    return ret;
  }

  private String getTraversalRulesPath(TraversalRules traversalRule) {
    if (traversalRule == null) return "(d:DataObject)";
    return switch (traversalRule) {
      case children -> "(d:DataObject)-[:has_child*0..]->(e:DataObject)";
      case parents -> "(d:DataObject)<-[:has_child*0..]-(e:DataObject)";
      case successors -> "(d:DataObject)-[:has_successor*0..]->(e:DataObject)";
      case predecessors -> "(d:DataObject)<-[:has_successor*0..]-(e:DataObject)";
      default -> "(d:DataObject)";
    };
  }

  protected String getSearchForReachableReferencesQuery(long collectionId, String userName) {
    String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)";
    ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
    ret += getWithPart("ns", "ret");
    ret += " WHERE id(col) = " + collectionId;
    ret += getReturnPart("ns", "ret", "col", userName);
    return ret;
  }

  protected String getSearchForReachableReferencesByShepardIdQuery(long collectionShepardId, String userName) {
    String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)";
    ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
    ret += getWithPart("ns", "ret");
    ret += " WHERE col." + Constants.SHEPARD_ID + " = " + collectionShepardId;
    ret += getReturnPart("ns", "ret", "col", userName);
    return ret;
  }

  protected String getSearchForReachableReferencesQuery(long collectionId, long startId, String userName) {
    String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)";
    ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
    ret += getWithPart("ns", "ret");
    ret += " WHERE id(d) = " + startId + " AND id(col) = " + collectionId;
    ret += getReturnPart("ns", "ret", "col", userName);
    return ret;
  }

  protected String getSearchForReachableReferencesByShepardIdQuery(
    long collectionShepardId,
    long startShepardId,
    String userName
  ) {
    String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)";
    ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
    ret += getWithPart("ns", "ret");
    ret +=
      " WHERE d." +
      Constants.SHEPARD_ID +
      " = " +
      startShepardId +
      " AND col." +
      Constants.SHEPARD_ID +
      " = " +
      collectionShepardId;
    ret += getReturnPart("ns", "ret", "col", userName);
    return ret;
  }

  private String getWithPart(String nodesVar, String retVar) {
    return " WITH nodes(path) as " + nodesVar + ", r as " + retVar;
  }

  private String getReturnPart(String nodesVar, String retVar, String collectionVar, String username) {
    String ret = "";
    ret += " AND NONE(node IN " + nodesVar + " WHERE (node.deleted = TRUE))";
    ret += " AND " + CypherQueryHelper.getReadableByQuery(collectionVar, username);
    ret += " " + CypherQueryHelper.getReturnPart(retVar, Neighborhood.EVERYTHING);
    return ret;
  }

  public abstract Class<T> getEntityType();
}