Descriptor.java
/*
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed 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.genesys.server.model.traits;
import com.fasterxml.jackson.annotation.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.genesys.blocks.annotations.NotCopyable;
import org.genesys.blocks.auditlog.annotations.Audited;
import org.genesys.blocks.model.*;
import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.filerepository.model.RepositoryImage;
import org.genesys.server.model.Partner;
import org.genesys.server.model.PublishState;
import org.genesys.server.model.dataset.Dataset;
import org.genesys.server.model.impl.Crop;
import org.genesys.server.model.impl.TranslatedUuidModel;
import org.genesys.server.model.vocab.ControlledVocabulary;
import org.genesys.server.model.vocab.VocabularyTerm;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A single descriptor definition belonging to a single "owner".
*
* @author Matija Obreza
*/
@Entity
@Cacheable
@Table(name = "descriptor")
@Audited
@Document(indexName = "descriptor")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid", scope = Descriptor.class)
@Getter
@Setter
@NoArgsConstructor
public class Descriptor extends TranslatedUuidModel<DescriptorLang, Descriptor> implements SelfCleaning, Publishable, Copyable<Descriptor>, AclAwareModel {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 7307818236681549484L;
/**
* Descriptor data types.
*/
public static enum DataType {
/** The numeric. */
NUMERIC,
/** Free text. */
TEXT,
/** Scale (0-9 scale) type. */
SCALE,
/** Coded descriptors using controlled vocabularies. */
CODED,
/** Yes/No type. */
BOOLEAN,
/** Dates. */
DATE
}
/**
* Descriptor classification.
*/
public static enum Category {
/** Passport descriptor. */
PASSPORT,
/** Management descriptor. */
MANAGEMENT,
/** Environment and Site descriptor. */
ENVIRONMENT,
/** Characterization descriptor. */
CHARACTERIZATION,
/** Evaluation descriptor. */
EVALUATION,
/** Abiotic stress descriptor. */
ABIOTICSTRESS,
/** Biotic stress descriptor. */
BIOTICSTRESS,
/** Molecular marker descriptor. */
MOLECULAR
}
/**
* User-specified version tag. E.g. "1.0", "1.1"
*/
@NotNull
@Column(nullable = false, updatable = false)
private String versionTag;
/**
* Trait title in English.
*/
@NotNull
@Column(nullable = false)
private String title;
/**
* Trait description in English.
*/
@Lob
private String description;
/** Descriptor data type. */
@NotNull
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private DataType dataType;
/** The key. */
@NotNull
@Column(name = "keyDescriptor", nullable = false)
private boolean key;
/** The publisher. */
@Size(max = 200)
@Column(length = 200)
private String publisher;
/** Not published by default. */
@Enumerated(EnumType.ORDINAL)
private PublishState state = PublishState.DRAFT;
/** Allow only integers, no decimal numbers. */
private Boolean integerOnly;
/** Data constraints: minimum allowed numeric values. */
@Column(name = "min_value")
private Double minValue;
/** Data constraints: maximum allowed numeric values. */
@Column(name = "max_value")
private Double maxValue;
/** The uom. */
@Size(max = 20)
@Column(name = "uom", length = 20)
private String uom;
/** The preferred column name in databases and spreadsheets. */
@Size(max = 50)
@Column(name = "columnName", length = 50)
private String columnName;
/** The bibliographic citation. */
@Column(name = "bibliographicCitation")
@Lob
private String bibliographicCitation;
/** The shared controlled vocabulary used by the descriptor */
@ManyToOne(cascade = {}, optional = true)
@JoinColumn(name = "vocabularyId")
@JsonIdentityReference(alwaysAsId = false)
@JsonIgnoreProperties(value = { "terms" })
@JsonView({ JsonViews.Public.class })
@Field(type = FieldType.Nested)
private ControlledVocabulary vocabulary;
/**
* Vocabulary terms specific to this descriptor (99% of the cases).
*/
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, orphanRemoval = true)
@JoinTable(name = "descriptor_term", joinColumns = @JoinColumn(name = "descriptorId"),
// other side
inverseJoinColumns = @JoinColumn(name = "termId"),
// Index
indexes = { @Index(columnList = "descriptorId, idx") },
// unique constraints
uniqueConstraints = { })
@OrderColumn(name = "idx")
@JsonView({ JsonViews.Public.class })
@Field(type = FieldType.Nested)
private List<VocabularyTerm> terms;
/** The owner. */
@NotNull
@JsonIdentityReference(alwaysAsId = false)
@ManyToOne(cascade = {}, optional = false)
@JoinColumn(name = "partnerId", updatable = false)
@JsonView({ JsonViews.Public.class })
@Field(type = FieldType.Object)
private Partner owner;
/** The descriptor lists. */
@JsonIdentityReference(alwaysAsId = false)
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "descriptors", cascade = { CascadeType.REFRESH })
@JsonIgnore
private List<DescriptorList> descriptorLists;
/** The datasets. */
@ManyToMany(fetch = FetchType.LAZY, cascade = {}, mappedBy = "descriptors")
@JsonIgnore
private List<Dataset> datasets;
/** The crop. */
@Size(max = Crop.CROP_SHORTNAME_LENGTH)
@Column(name = "crop", length = Crop.CROP_SHORTNAME_LENGTH, nullable = true)
private String crop;
/** Descriptor classification. */
@NotNull
@Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false)
private Category category;
@OneToOne(fetch = FetchType.LAZY, cascade = { }, optional = true, orphanRemoval = false)
@JoinColumn(name = "imageId", unique = true)
private RepositoryImage image;
@Column(name = "firstPublishedDate")
@NotCopyable
private Instant firstPublishedDate;
/**
* Pre-persist, pre-update
*/
@PrePersist
@PreUpdate
private void preupdate() {
if (Objects.isNull(firstPublishedDate) && Objects.equals(state, PublishState.PUBLISHED)) {
firstPublishedDate = Instant.now();
}
trimStringsToNull();
}
@PreRemove
private void preventRemoveWithImage() {
if (image != null) {
throw new DataIntegrityViolationException("Refusing to delete Descriptor with image");
}
}
/**
* Owner is the ACL parent object for the descriptor
*/
@Override
public AclAwareModel aclParentObject() {
return null;
}
/**
* Instantiates a new descriptor.
*
* @param descriptor the descriptor
*/
public Descriptor(final Descriptor descriptor) {
this(descriptor.getVersionTag(), descriptor.getTitle(), descriptor.description, descriptor.getDataType(), descriptor.getState(), descriptor.isKey(), descriptor
.getIntegerOnly(), descriptor.getMinValue(), descriptor.getMaxValue(), descriptor.getColumnName(), descriptor.getUom(), descriptor.getVocabulary(), descriptor
.getOwner(), descriptor.getDescriptorLists(), descriptor.getImage());
}
/**
* Instantiates a new descriptor.
*
* @param versionTag the version tag
* @param title the title
* @param description the description
* @param dataType the data type
* @param state the publish state
* @param key the key
* @param integerOnly the integer only
* @param minValue the min value
* @param maxValue the max value
* @param columnName the column name
* @param uom the uom
* @param vocabulary the vocabulary
* @param owner the owner
* @param descriptorLists the descriptor lists
* @param image the image
*/
public Descriptor(final String versionTag, final String title, final String description, final DataType dataType, final PublishState state, final boolean key,
final Boolean integerOnly, final Double minValue, final Double maxValue, final String columnName, final String uom, final ControlledVocabulary vocabulary,
final Partner owner, final List<DescriptorList> descriptorLists, final RepositoryImage image) {
this.versionTag = versionTag;
this.title = title;
this.description = description;
this.dataType = dataType;
this.state = state;
this.key = key;
this.integerOnly = integerOnly;
this.minValue = minValue;
this.maxValue = maxValue;
this.columnName = columnName;
this.uom = uom;
this.vocabulary = vocabulary;
this.owner = owner;
this.descriptorLists = descriptorLists;
this.image = image;
}
/**
* Checks if is published.
*
* @return the published
*/
@Override
public boolean isPublished() {
return this.state == PublishState.PUBLISHED;
}
/*
* (non-Javadoc)
* @see org.genesys.blocks.model.Copyable#copy()
*/
@Override
public Descriptor copy() {
return null;
}
/*
* (non-Javadoc)
* @see org.genesys.blocks.model.Copyable#apply(java.lang.Object)
*/
@Override
public Descriptor apply(final Descriptor source) {
Copyable.super.apply(source);
if (source.getTerms() != null) {
if (this.terms == null) {
this.terms = new ArrayList<>(source.getTerms());
} else {
this.terms.clear();
this.terms.addAll(source.getTerms());
}
}
// Check that CODED has terms or vocabulary
if (this.dataType == DataType.CODED && (this.terms == null || this.terms.size() == 0) && this.vocabulary == null) {
throw new DataIntegrityViolationException("Coded descriptor " + this.title + " requires terms or a vocabulary");
}
// Check that SCALE has terms
if (this.dataType == DataType.SCALE && (this.terms == null || this.terms.size() == 0)) {
throw new DataIntegrityViolationException("Scale descriptor " + this.title + " requires terms");
}
// Check that SCALE has min and max values
if (this.dataType == DataType.SCALE && (this.minValue == null || this.maxValue == null)) {
throw new DataIntegrityViolationException("Scale descriptor " + this.title + " requires min and max values.");
}
switch (this.dataType) {
case BOOLEAN:
this.setVocabulary(null);
this.setMinValue(null);
this.setMaxValue(null);
break;
case CODED:
this.setMinValue(null);
this.setMaxValue(null);
break;
case DATE:
case TEXT:
case NUMERIC:
case SCALE:
this.setVocabulary(null);
break;
default:
break;
}
// When using a specified vocabulary
if (this.vocabulary != null && this.terms != null) {
// We don't use our terms
this.terms.clear();
}
return this;
}
// @Override // TODO Introduce LazyLoadable
public void lazyLoad() {
if (this.getOwner() != null) {
this.getOwner().getId();
// System.err.println("Owner is " + this.getOwner());
} else {
// System.err.println("Owner is null");
}
if (this.getDescriptorLists() != null) {
this.getDescriptorLists().size();
}
if (this.getDataType() == DataType.CODED || this.getDataType() == DataType.SCALE) {
if (this.getVocabulary() != null) {
this.getVocabulary().getId();
if (this.getVocabulary().getTerms() != null) {
this.getVocabulary().getTerms().size();
}
this.setTerms(null);
}
if (this.getTerms() != null) {
this.getTerms().size();
}
} else {
// clear terms list
if (this.getTerms() != null) {
this.getTerms().clear();
}
}
if (this.getImage() != null) {
this.getImage().getId();
}
}
@Override
public boolean canEqual(Object other) {
return other instanceof Descriptor;
}
}