RepositoryFile.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.filerepository.model;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.UUID;

import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable;
import org.genesys.blocks.model.SelfCleaning;
import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.filerepository.metadata.BaseMetadata;
import org.genesys.filerepository.service.BytesStorageService;
import org.hibernate.annotations.Type;
import org.springframework.data.domain.Sort;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

/**
 * The RepositoryFile.
 */
@Cacheable
@Entity
@Table(name = "repository_file",
		// indexes
		indexes = { @Index(unique = false, columnList = "folder_id", name = "IX_repoFile_path") }
		// unique
		, uniqueConstraints = { @UniqueConstraint(columnNames = { "folder_id", "originalFilename" }) })
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
@Setter
public class RepositoryFile extends AuditedVersionedModel implements AclAwareModel, BaseMetadata, Copyable<RepositoryFile>, SelfCleaning {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 568963297049937168L;

	/** Default sort order */
	public static final Sort DEFAULT_SORT = Sort.by("originalFilename");

	/** The uuid. */
	@Column(unique = true, nullable = false, updatable = false)
	@Type(type = "uuid-binary")
	private UUID uuid;

	/**
	 * The repository folder.
	 *
	 * @since 1.1
	 */
	@ManyToOne(cascade = {}, fetch = FetchType.LAZY, optional = false)
	@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "path")
	@JsonIdentityReference(alwaysAsId = true)
	@JsonProperty(access = Access.READ_ONLY)
	private RepositoryFolder folder;

	/** The original filename as provided by the end user. */
	@Column(nullable = false, length = 250)
	private String originalFilename;

	/** Extension based on originalFilename. */
	@Column(length = 50)
	private String extension;

	/** The title. */
	@Column(length = 200)
	private String title;

	/** The subject. */
	@Column
	@Lob
	@Type(type = "org.hibernate.type.TextType")
	private String subject;

	/** The description. */
	@Column
	@Lob
	@Type(type = "org.hibernate.type.TextType")
	private String description;

	/** The creator. */
	@Column(length = 200)
	private String creator;

	/** The created. */
	@Column(length = 200)
	private String created;

	/** The rights holder. */
	@Column(length = 200)
	private String rightsHolder;

	/** The access rights. */
	@Column(length = 200)
	private String accessRights;

	/** The license. */
	@Column(length = 50)
	private String license;

	/** The content type. */
	@Column(length = 200)
	private String contentType;

	/** The extent. */
	@Column(length = 200)
	private String extent;

	/** The bibliographic citation. */
	@Column
	@Lob
	@Type(type = "org.hibernate.type.TextType")
	private String bibliographicCitation;

	/** URL where the resource was originally retrieved from. */
	@Column(length = 500)
	private String originalUrl;

	/**
	 * For resources retrieved from {@link #originalUrl} we maintain the date of
	 * retrieval.
	 */
	@Column
	private Instant dateRetrieved;

	/** Sha1sum hash of the bytes. */
	@Column(length = 40, nullable = false)
	@JsonProperty(access = Access.READ_ONLY)
	private String sha1Sum;

	/** Sha1sum hash of the bytes. */
	@Column(length = 48, nullable = true)
	@JsonProperty(access = Access.READ_ONLY)
	private byte[] sha384;

	/** MD5 hash of the bytes. */
	@Column(length = 32, nullable = false)
	@JsonProperty(access = Access.READ_ONLY)
	private String md5Sum;

	/** Byte length. */
	@Column(nullable = false)
	private int size;

	/** URN identifier generated from the UUID */
	@Transient
	private String identifier;

	/** Name of file in BytesStorageService */
	@Transient
	private String filename;

	/** Name of metadata file in BytesStorageService */
	@Transient
	private String metadataFilename;

	/** Folder in BytesStorageService where bytes are saved */
	@Transient
	private String storageFolder;

	/** Full path (including filename) where bytes are located in BytesStorageService */
	@Transient
	private String storagePath;

	/**
	 * Before persist and any update
	 */
	@PrePersist
	@PreUpdate
	protected void prePersist() {
		if (uuid == null) {
			uuid = UUID.randomUUID();
		}

		trimStringsToNull();
	}

	/**
	 * Calculate transient fields
	 */
	@PostLoad
	@PostUpdate
	@PostPersist
	protected void postLoad() {
		if (this.uuid != null) {
			var uuidStr = uuid.toString();
			this.identifier = "urn:uuid:" + uuidStr;
			this.filename = uuidStr + StringUtils.defaultIfBlank(extension, "");
			this.metadataFilename = uuidStr + (StringUtils.equals(".json", extension) ? extension : "") + ".json";
			this.storageFolder = "/" + String.valueOf(uuid).substring(0, 3);
			this.storagePath = this.storageFolder + "/" + this.filename;
		}
	}

	/**
	 * For repository files, the parent object is always a {@link RepositoryFolder}
	 * (can't be null).
	 */
	@Override
	public AclAwareModel aclParentObject() {
		return this.folder;
	}

	/*
	 * (non-Javadoc)
	 * @see org.genesys.filerepository.metadata.BaseMetadata#getIdentifier()
	 */
	@Override
	public String getIdentifier() {
		return this.identifier;
	}

	/**
	 * Gets the filename as used by {@link BytesStorageService}.
	 *
	 * @return the filename
	 */
	public String getFilename() {
		return this.filename;
	}

	/**
	 * Gets the filename for metadata as saved in {@link BytesStorageService}.
	 *
	 * @return the filename
	 */
	public String getMetadataFilename() {
		return this.metadataFilename;
	}

	/**
	 * Get the path of the file used by {@link BytesStorageService}.
	 *
	 * @return the storage path
	 */
	@JsonGetter
	public String getStorageFolder() {
		return this.storageFolder;
	}

	/**
	 * Get the full path to the file as used by {@link BytesStorageService}. This is
	 * the concatenation of {@link #getStorageFolder()} and {@link #getFilename()}.
	 *
	 * @return the storage full path
	 */
	@JsonGetter
	public String getStoragePath() {
		return this.storagePath;
	}

	/**
	 * Get the {@link Path} version of the full path to the file as used by {@link BytesStorageService}.
	 * @return the full path of file in storage
	 */
	public Path storagePath() {
		if (uuid == null) {
			return null;
		}
		return Paths.get(getStorageFolder(), getFilename());
	}

	/**
	 * Get {@link Path} representation of the {@link #getStorageFolder()}
	 * 
	 * @return a {@code Path}
	 */
	public Path storageFolder() {
		if (uuid == null) {
			return null;
		}
		return Paths.get(getStorageFolder());
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.genesys.filerepository.metadata.BaseMetadata#getFormat()
	 */
	@Override
	public String getFormat() {
		return getContentType();
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.genesys.filerepository.metadata.BaseMetadata#getDateSubmitted ()
	 */
	@Override
	public Instant getDateSubmitted() {
		return getCreatedDate();
	}

	/*
	 * (non-Javadoc)
	 * @see org.genesys.filerepository.metadata.BaseMetadata#getModified()
	 */
	@Override
	public Instant getModified() {
		return getLastModifiedDate();
	}

	/**
	 * Sets the original filename.
	 *
	 * @param originalFilename the new original filename
	 */
	public void setOriginalFilename(final String originalFilename) {
		if (originalFilename != null) {
			final int dotIndex = originalFilename.lastIndexOf('.');
			setExtension(dotIndex > 0 ? originalFilename.substring(dotIndex) : null);
		}
		this.originalFilename = originalFilename;
	}

	/*
	 * (non-Javadoc)
	 * @see org.genesys.blocks.model.Copyable#apply(java.lang.Object)
	 */
	@Override
	public RepositoryFile apply(final RepositoryFile source) {
		if (StringUtils.isNotBlank(source.originalFilename)) {
			// also updates extension
			setOriginalFilename(source.originalFilename);
		}

		this.active = source.active;
		// this.folder = source.folder;
		this.accessRights = source.accessRights;
		this.bibliographicCitation = source.bibliographicCitation;
		this.contentType = source.contentType;
		this.created = source.created;
		this.creator = source.creator;
		this.dateRetrieved = source.dateRetrieved;
		this.description = source.description;
		this.extent = source.extent;
		this.license = source.license;
		this.originalUrl = source.originalUrl;
		this.rightsHolder = source.rightsHolder;
		this.subject = source.subject;
		this.title = source.title;

		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.genesys.blocks.model.Copyable#copy()
	 */
	@Override
	public RepositoryFile copy() {
		final RepositoryFile copy = new RepositoryFile();
		copy.apply(this);

		// copy.id=this.id;
		// copy.uuid=this.uuid;
		// copy.setVersion(this.getVersion());

		copy.sha1Sum = this.sha1Sum;
		copy.md5Sum = this.md5Sum;
		copy.size = this.size;
		copy.extension = this.extension;

		return copy;
	}

	/**
	 * Copy meta data.
	 *
	 * @param <T> the generic type
	 * @param target the result file
	 */
	public <T extends RepositoryFile> void copyMetaData(final T target) {
		target.setContentType(this.getContentType());
		target.setDescription(this.getDescription());
		target.setOriginalFilename(this.getOriginalFilename());
		target.setAccessRights(this.getAccessRights());
		target.setBibliographicCitation(this.getBibliographicCitation());
		target.setCreated(this.getCreated());
		target.setCreatedDate(this.getCreatedDate());
		target.setCreator(this.getCreator());
		target.setExtent(this.getExtent());
		target.setLastModifiedDate(this.getLastModifiedDate());
		target.setLicense(this.getLicense());
		target.setFolder(this.getFolder());
		target.setRightsHolder(this.getRightsHolder());
		target.setSubject(this.getSubject());
		target.setTitle(this.getTitle());
	}

	@Override
	public boolean canEqual(Object other) {
		return other instanceof RepositoryFile;
	}
}