package models; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.Version; import org.apache.commons.lang3.StringUtils; import com.avaje.ebean.Expr; import com.avaje.ebean.ExpressionList; import com.avaje.ebean.Page; import com.avaje.ebean.Query; import com.fasterxml.jackson.annotation.JsonIgnore; import controllers.Portals; import controllers.Users; import controllers.WaybackController; import controllers.routes; import play.Play; import play.data.format.Formats.DateTime; import play.data.validation.Constraints.Required; import play.data.validation.ValidationError; import play.db.ebean.Model; @Entity public class Document extends Model { /** */ private static final long serialVersionUID = 4697602797902508209L; @Id public Long id; @ManyToOne @JsonIgnore @JoinColumn(name="id_watched_target") public WatchedTarget watchedTarget; @Version public Timestamp updatedAt; public String waybackTimestamp; public Status status; public Date currentStatusSet; @OneToOne(mappedBy="document", cascade=CascadeType.REMOVE) @JsonIgnore public Book book; @OneToOne(mappedBy="document", cascade=CascadeType.REMOVE) @JsonIgnore public Journal journal; @ManyToMany(cascade=CascadeType.ALL) @JsonIgnore @JoinTable(name = "portal_document", joinColumns = { @JoinColumn(name = "id_document", referencedColumnName="id") }, inverseJoinColumns = { @JoinColumn(name = "id_portal", referencedColumnName="id") }) public List<Portal> portals = new ArrayList<>(); @ManyToMany(cascade=CascadeType.REMOVE) @JsonIgnore @JoinTable(name = "fast_subject_document", joinColumns = { @JoinColumn(name = "id_document", referencedColumnName="id") }, inverseJoinColumns = { @JoinColumn(name = "id_fast_subject", referencedColumnName="id") }) public List<FastSubject> fastSubjects = new ArrayList<>(); public String landingPageUrl; public String documentUrl; public String sha256Hash; public String ctpHash; @Column(columnDefinition = "text") public String title; @Column(columnDefinition = "text") public String title2; public String doi; /* The Logical ARK for the Logical Document */ public String ark; /* The Logical MD-ARK for the Document Metadata (not used by eJournals here)*/ public String md_ark; /* The Logical D-ARK for the Document Data (bitstream) */ public String d_ark; /* The Logical D-ARK for the Mets Document */ public String mets_d_ark; @DateTime(pattern="dd-MM-yyyy") public Date publicationDate; @Required public Integer publicationYear; @Required public String filename; public Long size; public boolean priorityCataloguing; public String type; public String author1Fn; public String author1Ln; public String author2Fn; public String author2Ln; public String author3Fn; public String author3Ln; public static final Model.Finder<Long, Document> find = new Model.Finder<>(Long.class, Document.class); public String htmlFilename() { return id + ".html"; } public List<ValidationError> validate() { List<ValidationError> errors = new ArrayList<ValidationError>(); String required = "This field is required"; // Title required unless a journal issue: if( ! this.isJournalIssue() ) { if( StringUtils.isEmpty(this.title)) { errors.add(new ValidationError("title", "A title is required for all documents except journal issues")); } } // Sub-cases if (isJournalArticleOrIssue()) { if (journal.journalTitleId == null) errors.add(new ValidationError("journal.journalTitleId", required)); if (journal.volume.isEmpty() && journal.issue.isEmpty()) errors.add(new ValidationError("journal.issue", "Complete Volume or Issue/Part or both")); } else if (isWholeBook()) { if (book.publisher.isEmpty()) errors.add(new ValidationError("book.publisher", required)); } return errors.isEmpty() ? null : errors; } public boolean isWholeBook() { return type != null && type.equals(Type.BOOK.toString()); } public boolean isBookChapter() { return type != null && type.equals(Type.BOOK_CHAPTER.toString()); } public boolean isJournalArticle() { return type != null && type.equals(Type.JOURNAL_ARTICLE.toString()); } public boolean isJournalIssue() { return type != null && type.equals(Type.JOURNAL_ISSUE.toString()); } public boolean isBookOrBookChapter() { return isWholeBook() || isBookChapter(); } public boolean isJournalArticleOrIssue() { return isJournalArticle() || isJournalIssue(); } public boolean isOwnedBy(User user) { if( user == null || this.watchedTarget.target.authorUser == null) { return (this.watchedTarget.target.authorUser == null); } return user.id.equals(watchedTarget.target.authorUser.id); } public boolean isEditableFor(User user) { return isOwnedBy(user) || user.hasExpertUserRights(); } public boolean hasPermissionForService(Portal portal) { List<License> licenses = watchedTarget.target.licenses; if (licenses.isEmpty()) return false; else return licenses.get(0).portals.contains(portal); } public void clearImproperFields() { if (isJournalIssue()) { author1Fn = author1Ln = author2Fn = author2Ln = author3Fn = author3Ln = ""; } } public static Query<Document> query(DocumentFilter documentFilter, String sortBy, String order, String filter) { ExpressionList<Document> el = find.where(); if (documentFilter.watchedtarget != null) { el = el.eq("id_watched_target", documentFilter.watchedtarget); } else if (documentFilter.user != null) { el = el.in("id_watched_target", Users.getWatchedTargetIds(documentFilter.user)); } if (documentFilter.service != null && !documentFilter.service.isEmpty()) { el = el.in("id", Portals.getDocumentIds(documentFilter.service)); } if (documentFilter.fastSubjects != null && !documentFilter.fastSubjects.isEmpty()) { el = el.in("fastSubjects.fastId", documentFilter.fastSubjects); } SimpleDateFormat waybackFormat = new SimpleDateFormat("yyyyMMdd"); if (documentFilter.startdate != null) { el = el.gt("waybackTimestamp", waybackFormat.format(documentFilter.startdate)); } if (documentFilter.enddate != null) { el = el.lt("waybackTimestamp", waybackFormat.format(documentFilter.enddate)); } return el.eq("status", documentFilter.status.ordinal()).add(Expr.or( Expr.icontains("documentUrl", filter), Expr.icontains("title", filter)) ).orderBy(sortBy + " " + order); } public static Page<Document> page(DocumentFilter documentFilter, int page, int pageSize, String sortBy, String order, String filter) { return query(documentFilter, sortBy, order, filter) .findPagingList(pageSize) .setFetchAhead(false) .getPage(page); } public String getType() { return this.type; } public void setStatus(Status status) { this.status = status; currentStatusSet = new Date(); } public boolean isSubmissable() { if( type != null ) { return true; } return false; } public String currentStatusSetUTCString() { SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); dateFormatGmt.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormatGmt.format(currentStatusSet); } public boolean hasARKs() { if( StringUtils.isEmpty(ark) || StringUtils.isEmpty(md_ark) || StringUtils.isEmpty(d_ark) || StringUtils.isEmpty(mets_d_ark) ){ return false; } return true; } public String waybackUrl() { return controllers.routes.WaybackController.wayback(waybackTimestamp + "/" + documentUrl).url(); } protected String internalWaybackUrl() { return WaybackController.getWaybackEndpoint() + waybackTimestamp + "/" + documentUrl; } public String globalAccessDocumentUrl() { return WaybackController.getAccessResolverEndpoint() + waybackTimestamp + "/" + documentUrl; } public String actualSourceUrl() { return waybackTimestamp != null ? waybackUrl() : documentUrl; } public static String getPdf2HtmlEndpoint() { return Play.application().configuration().getString("application.pdftohtmlex.url"); } public String pdf2htmlUrl() throws UnsupportedEncodingException { return getPdf2HtmlEndpoint()+URLEncoder.encode(this.internalWaybackUrl(), "UTF-8"); } public enum Type { BOOK ("Book"), BOOK_CHAPTER ("Book Chapter"), JOURNAL_ARTICLE ("Journal Article"), JOURNAL_ISSUE ("Journal Issue"); private final String name; Type(String name) { this.name = name; } @Override public String toString() { return name; } } public enum Status { NEW, SAVED, SUBMITTED, IGNORED, DELETED; } @Override public String toString() { return "Document [id=" + id + ", waybackTimestamp=" + waybackTimestamp + ", status=" + status + ", currentStatusSet=" + currentStatusSet + ", book=" + book + ", journal=" + journal + ", portals=" + portals + ", fastSubjects=" + fastSubjects + ", landingPageUrl=" + landingPageUrl + ", documentUrl=" + documentUrl + ", sha256Hash=" + sha256Hash + ", ctpHash=" + ctpHash + ", title=" + title + ", doi=" + doi + ", ark=" + ark + ", publicationDate=" + publicationDate + ", publicationYear=" + publicationYear + ", filename=" + filename + ", size=" + size + ", priorityCataloguing=" + priorityCataloguing + ", type=" + type + ", author1Fn=" + author1Fn + ", author1Ln=" + author1Ln + ", author2Fn=" + author2Fn + ", author2Ln=" + author2Ln + ", author3Fn=" + author3Fn + ", author3Ln=" + author3Ln + "]"; } }