/* * Copyright 2016-2017 Hewlett Packard Enterprise Development Company, L.P. * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. */ package com.hp.autonomy.frontend.find.core.savedsearches; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDefs; import org.jadira.usertype.dateandtime.joda.PersistentDateTime; import org.joda.time.DateTime; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EntityListeners; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.Transient; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @Entity @Table(name = SavedSearch.Table.NAME) @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "search_type") @EntityListeners(AuditingEntityListener.class) @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @TypeDefs(@TypeDef(name = SavedSearch.JADIRA_TYPE_NAME, typeClass = PersistentDateTime.class)) @Access(AccessType.FIELD) public abstract class SavedSearch<T extends SavedSearch<T, B>, B extends SavedSearch.Builder<T, B>> { public static final String JADIRA_TYPE_NAME = "jadira"; @Id @Column(name = Table.Column.ID) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @SuppressWarnings("InstanceVariableOfConcreteClass") @CreatedBy @ManyToOne @JoinColumn(name = Table.Column.USER_ID) @JsonIgnore private UserEntity user; private String title; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = IndexesTable.NAME, joinColumns = @JoinColumn(name = IndexesTable.Column.SEARCH_ID)) private Set<EmbeddableIndex> indexes; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = ParametricValuesTable.NAME, joinColumns = @JoinColumn(name = ParametricValuesTable.Column.SEARCH_ID)) private Set<FieldAndValue> parametricValues; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = ParametricRangesTable.NAME, joinColumns = @JoinColumn(name = ParametricRangesTable.Column.SEARCH_ID)) private Set<ParametricRange> parametricRanges; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = ConceptClusterPhraseTable.NAME, joinColumns = @JoinColumn(name = ConceptClusterPhraseTable.Column.SEARCH_ID)) private Set<ConceptClusterPhrase> conceptClusterPhrases; @Column(name = Table.Column.START_DATE) @Type(type = JADIRA_TYPE_NAME) private DateTime minDate; @Column(name = Table.Column.END_DATE) @Type(type = JADIRA_TYPE_NAME) private DateTime maxDate; @CreatedDate @Column(name = Table.Column.CREATED_DATE) @Type(type = JADIRA_TYPE_NAME) private DateTime dateCreated; @LastModifiedDate @Column(name = Table.Column.MODIFIED_DATE) @Type(type = JADIRA_TYPE_NAME) private DateTime dateModified; @Transient private DateRange dateRange; @Column(name = Table.Column.ACTIVE) @JsonIgnore private Boolean active; @Column(name = Table.Column.MIN_SCORE, nullable = false) private Integer minScore = 0; protected SavedSearch(final Builder<?, ?> builder) { id = builder.id; title = builder.title; indexes = builder.indexes; parametricValues = builder.parametricValues; parametricRanges = builder.parametricRanges; conceptClusterPhrases = builder.conceptClusterPhrases; minDate = builder.minDate; maxDate = builder.maxDate; dateCreated = builder.dateCreated; dateModified = builder.dateModified; dateRange = builder.dateRange; active = builder.active; minScore = builder.minScore; } /** * Merge client-mutable SavedSearch-implementation specific fields from the other search into this one. */ protected abstract void mergeInternal(T other); public abstract B toBuilder(); /** * Merge client-mutable fields from the other search into this one. */ @SuppressWarnings("OverlyComplexMethod") public void merge(final T other) { if(other != null) { mergeInternal(other); title = other.getTitle() == null ? title : other.getTitle(); minDate = other.getMinDate() == null ? minDate : other.getMinDate(); maxDate = other.getMaxDate() == null ? maxDate : other.getMaxDate(); minScore = other.getMinScore() == null ? minScore : other.getMinScore(); dateRange = other.getDateRange() == null ? dateRange : other.getDateRange(); indexes = other.getIndexes() == null ? indexes : other.getIndexes(); parametricValues = other.getParametricValues() == null ? parametricValues : other.getParametricValues(); parametricRanges = other.getParametricRanges() == null ? parametricRanges : other.getParametricRanges(); if(other.getConceptClusterPhrases() != null) { conceptClusterPhrases.clear(); conceptClusterPhrases.addAll(other.getConceptClusterPhrases()); } } } @SuppressWarnings("unused") @Access(AccessType.PROPERTY) @Column(name = Table.Column.DATE_RANGE_TYPE) @JsonIgnore public Integer getDateRangeInt() { return dateRange == null ? null : dateRange.getId(); } @SuppressWarnings("unused") public void setDateRangeInt(final Integer dateRangeInt) { dateRange = DateRange.getType(dateRangeInt); } // WARNING: This logic is duplicated in the client-side search-data-util // Caution: Method has multiple exit points. public String toQueryText() { if(conceptClusterPhrases.isEmpty()) { return "*"; } else { final Collection<List<ConceptClusterPhrase>> groupedClusters = conceptClusterPhrases.stream() .collect(Collectors.groupingBy(ConceptClusterPhrase::getClusterId)).values(); return groupedClusters.stream() .map(clusterList -> clusterList.stream() .sorted() .map(ConceptClusterPhrase::getPhrase).collect(toList())) .map(clusterPhrases -> wrapInBrackets(StringUtils.join(clusterPhrases, ' '))) .collect(joining(" AND ")); } } private String wrapInBrackets(final String input) { return input.isEmpty() ? input : '(' + input + ')'; } protected interface Table { String NAME = "searches"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String ID = "search_id"; String USER_ID = "user_id"; String START_DATE = "start_date"; String END_DATE = "end_date"; String CREATED_DATE = "created_date"; String MODIFIED_DATE = "modified_date"; String ACTIVE = "active"; String TOTAL_RESULTS = "total_results"; String DATE_RANGE_TYPE = "date_range_type"; String MIN_SCORE = "min_score"; } } private interface IndexesTable { String NAME = "search_indexes"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String SEARCH_ID = "search_id"; } } private interface ParametricValuesTable { String NAME = "search_parametric_values"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String SEARCH_ID = "search_id"; } } private interface ParametricRangesTable { String NAME = "search_parametric_ranges"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String SEARCH_ID = "search_id"; } } protected interface StoredStateTable { String NAME = "search_stored_state"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String SEARCH_ID = "search_id"; } } interface ConceptClusterPhraseTable { String NAME = "search_concept_cluster_phrases"; @SuppressWarnings("InnerClassTooDeeplyNested") interface Column { String ID = "search_concept_cluster_phrase_id"; String SEARCH_ID = "search_id"; String PHRASE = "phrase"; String PRIMARY = "primary_phrase"; String CLUSTER_ID = "cluster_id"; } } @NoArgsConstructor @Getter public abstract static class Builder<T extends SavedSearch<T, B>, B extends Builder<T, B>> { private Long id; private String title; private Set<EmbeddableIndex> indexes; private Set<FieldAndValue> parametricValues; private Set<ParametricRange> parametricRanges; private Set<ConceptClusterPhrase> conceptClusterPhrases; private DateTime minDate; private DateTime maxDate; private DateTime dateCreated; private DateTime dateModified; private DateRange dateRange; private Boolean active = true; private Integer minScore; protected Builder(final SavedSearch<T, B> search) { id = search.id; title = search.title; indexes = search.indexes; parametricValues = search.parametricValues; parametricRanges = search.parametricRanges; conceptClusterPhrases = search.conceptClusterPhrases; minDate = search.minDate; maxDate = search.maxDate; dateCreated = search.dateCreated; dateModified = search.dateModified; dateRange = search.dateRange; active = search.active; minScore = search.minScore; } public abstract T build(); public Builder<T, B> setId(final Long id) { this.id = id; return this; } public Builder<T, B> setTitle(final String title) { this.title = title; return this; } public Builder<T, B> setIndexes(final Set<EmbeddableIndex> indexes) { this.indexes = new LinkedHashSet<>(indexes); return this; } public Builder<T, B> setParametricValues(final Set<FieldAndValue> parametricValues) { this.parametricValues = new LinkedHashSet<>(parametricValues); return this; } public Builder<T, B> setParametricRanges(final Set<ParametricRange> parametricRanges) { this.parametricRanges = new LinkedHashSet<>(parametricRanges); return this; } public Builder<T, B> setConceptClusterPhrases(final Set<ConceptClusterPhrase> conceptClusterPhrases) { this.conceptClusterPhrases = new LinkedHashSet<>(conceptClusterPhrases); return this; } public Builder<T, B> setMinDate(final DateTime minDate) { this.minDate = minDate; return this; } public Builder<T, B> setMaxDate(final DateTime maxDate) { this.maxDate = maxDate; return this; } @SuppressWarnings("unused") public Builder<T, B> setDateCreated(final DateTime dateCreated) { this.dateCreated = dateCreated; return this; } @SuppressWarnings("unused") public Builder<T, B> setDateModified(final DateTime dateModified) { this.dateModified = dateModified; return this; } public Builder<T, B> setDateRange(final DateRange dateRange) { this.dateRange = dateRange; return this; } public Builder<T, B> setActive(final Boolean active) { this.active = active; return this; } public Builder<T, B> setMinScore(final Integer minScore) { this.minScore = minScore; return this; } } }