TMFeatureSource.java

/*
 * Copyright (C) 2023 B3Partners B.V.
 *
 * SPDX-License-Identifier: MIT
 */
package org.tailormap.api.persistence;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.envers.Audited;
import org.hibernate.type.SqlTypes;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.tailormap.api.persistence.json.JDBCConnectionProperties;
import org.tailormap.api.persistence.json.ServiceAuthentication;
import org.tailormap.api.persistence.json.TMServiceCaps;
import org.tailormap.api.persistence.listener.EntityEventPublisher;

@Audited
@Entity
@Table(name = "feature_source")
@EntityListeners({EntityEventPublisher.class, AuditingEntityListener.class})
public class TMFeatureSource extends AuditMetadata {

  public enum Protocol {
    WFS("wfs"),

    JDBC("jdbc");

    private final String value;

    Protocol(String value) {
      this.value = value;
    }

    public String getValue() {
      return value;
    }

    @Override
    public String toString() {
      return String.valueOf(value);
    }

    public static TMFeatureSource.Protocol fromValue(String value) {
      for (TMFeatureSource.Protocol p : TMFeatureSource.Protocol.values()) {
        if (p.value.equals(value)) {
          return p;
        }
      }
      throw new IllegalArgumentException("Unexpected value '" + value + "'");
    }
  }

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Version
  private Long version;

  @Transient
  private boolean refreshCapabilities;

  @Column(columnDefinition = "text")
  private String notes;

  @NotNull @Enumerated(EnumType.STRING)
  private TMFeatureSource.Protocol protocol;

  @Basic
  private String title;

  @Column(length = 2048)
  private String url;

  @JdbcTypeCode(SqlTypes.JSON)
  @Column(columnDefinition = "jsonb")
  private JDBCConnectionProperties jdbcConnection;

  @JdbcTypeCode(SqlTypes.JSON)
  @Column(columnDefinition = "jsonb")
  private ServiceAuthentication authentication;

  @ManyToOne
  @JoinColumn(name = "linked_service")
  private GeoService linkedService;

  @JdbcTypeCode(SqlTypes.JSON)
  @Column(columnDefinition = "jsonb")
  private TMServiceCaps serviceCapabilities;

  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinTable(
      name = "feature_source_feature_types",
      inverseJoinColumns = @JoinColumn(name = "feature_type"),
      joinColumns = @JoinColumn(name = "feature_source", referencedColumnName = "id"))
  @OrderBy("name asc")
  private List<TMFeatureType> featureTypes = new ArrayList<>();

  @Override
  public String toString() {
    try {
      return "TMFeatureSource{"
          + "id="
          + id
          + ", protocol="
          + protocol
          + ", title='"
          + title
          + '\''
          + ", url='"
          + url
          + '\''
          + ", jdbcConnection="
          + new ObjectMapper().writeValueAsString(jdbcConnection)
          + '}';
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }
  }

  // <editor-fold desc="getters and setters">
  public Long getId() {
    return id;
  }

  public TMFeatureSource setId(Long id) {
    this.id = id;
    return this;
  }

  public Long getVersion() {
    return version;
  }

  public TMFeatureSource setVersion(Long version) {
    this.version = version;
    return this;
  }

  public boolean isRefreshCapabilities() {
    return refreshCapabilities;
  }

  public void setRefreshCapabilities(boolean refreshCapabilities) {
    this.refreshCapabilities = refreshCapabilities;
  }

  public String getNotes() {
    return notes;
  }

  public TMFeatureSource setNotes(String notes) {
    this.notes = notes;
    return this;
  }

  public Protocol getProtocol() {
    return protocol;
  }

  public TMFeatureSource setProtocol(Protocol protocol) {
    this.protocol = protocol;
    return this;
  }

  public String getTitle() {
    return title;
  }

  public TMFeatureSource setTitle(String title) {
    this.title = title;
    return this;
  }

  public String getUrl() {
    return url;
  }

  public TMFeatureSource setUrl(String url) {
    this.url = url;
    return this;
  }

  public JDBCConnectionProperties getJdbcConnection() {
    return jdbcConnection;
  }

  public TMFeatureSource setJdbcConnection(JDBCConnectionProperties jdbcConnection) {
    this.jdbcConnection = jdbcConnection;
    return this;
  }

  public ServiceAuthentication getAuthentication() {
    return authentication;
  }

  public TMFeatureSource setAuthentication(ServiceAuthentication authentication) {
    this.authentication = authentication;
    return this;
  }

  public GeoService getLinkedService() {
    return linkedService;
  }

  public TMFeatureSource setLinkedService(GeoService linkedService) {
    this.linkedService = linkedService;
    return this;
  }

  public TMServiceCaps getServiceCapabilities() {
    return serviceCapabilities;
  }

  public TMFeatureSource setServiceCapabilities(TMServiceCaps serviceCapabilities) {
    this.serviceCapabilities = serviceCapabilities;
    return this;
  }

  // Added this extra getter to work around Spring Data Rest
  // By adding a repository for feature types SDR does not add feature types when fetching a feature
  // source, while the front-end currently depends on having all feature types available.
  // In the future we could refactor this to give only a list of names and fetch the type itself
  // when needed.
  public List<TMFeatureType> getAllFeatureTypes() {
    return featureTypes;
  }

  public List<TMFeatureType> getFeatureTypes() {
    return featureTypes;
  }

  public TMFeatureSource setFeatureTypes(List<TMFeatureType> featureTypes) {
    this.featureTypes = featureTypes;
    return this;
  }

  // </editor-fold>

  public TMFeatureType findFeatureTypeByName(String featureTypeName) {
    return getFeatureTypes().stream()
        .filter(ft -> featureTypeName.equals(ft.getName()))
        .findFirst()
        .orElse(null);
  }
}