package org.fluxtream.core.domain; import org.fluxtream.core.aspects.FlxLogger; import org.fluxtream.core.connectors.ObjectType; import org.fluxtream.core.connectors.annotations.ObjectTypeSpec; import org.fluxtream.core.utils.JPAUtils; import org.hibernate.annotations.Index; import org.hibernate.annotations.Type; import org.jetbrains.annotations.Nullable; import org.joda.time.DateTimeUtils; import javax.persistence.*; import java.util.*; @MappedSuperclass public abstract class AbstractFacet extends AbstractEntity { private static final FlxLogger LOG_DEBUG = FlxLogger.getLogger("Fluxtream"); protected static final String TAG_DELIMITER = ","; public AbstractFacet() { this.timeUpdated = System.currentTimeMillis(); figureOutObjectType(); } private void figureOutObjectType() { ObjectTypeSpec objectType = this.getClass().getAnnotation(ObjectTypeSpec.class); if (objectType!=null) this.objectType = objectType.value(); else this.objectType = -1; } public AbstractFacet(Long apiKeyId) { this.timeUpdated = System.currentTimeMillis(); this.apiKeyId = apiKeyId; figureOutObjectType(); } @Index(name = "apiKey") public Long apiKeyId; @Index(name="guestId_index") public long guestId; @Type(type="yes_no") @Index(name="isEmpty_index") public boolean isEmpty = false; @Index(name="timeUpdated_index") public long timeUpdated; @Index(name="start_index") public long start; @Index(name="end_index") public long end; @Index(name="api_index") public int api; @Index(name="objectType_index") public int objectType; /** * A string representation of the tags for this facet. You should NEVER set this field directly. Instead, always * use the {@link #addTags} method which sets both this and the {@link #tagSet} fields. */ @Lob public String tags; /** * A {@link Set} representation of the tags for this facet. You should NEVER set this field directly. Instead, * always use the {@link #addTags} method which sets both this and the {@link #tags} fields. */ public transient Set<Tag> tagSet; @Lob public String comment; @Lob public String fullTextDescription; @PostLoad void loadTags() { if (tags == null || tags.equals("")) { return; } StringTokenizer st = new StringTokenizer(tags,", \t\n\r\f"); while (st.hasMoreTokens()) { String tag = st.nextToken().trim(); if (tag.length() > 0) { addTag(tag); } } } private void addTag(final String tagName) { if (tagName != null && tagName.length() > 0) { if (tagSet == null) { tagSet = new HashSet<Tag>(); } Tag tag = new Tag(); tag.name = tagName; tagSet.add(tag); } } protected void persistTags() { buildTagsStringFromTagsSet(); } private void buildTagsStringFromTagsSet() { if (tagSet == null) { return; } if (tagSet.size() > 0) { final StringBuilder sb = new StringBuilder(TAG_DELIMITER); for (final Tag tag : tagSet) { if (tag.name.length() > 0) { sb.append(tag.name).append(TAG_DELIMITER); } } if (sb.length() > 1) { tags = sb.toString(); } } else { tags = ""; } } @PrePersist @PreUpdate protected void setFullTextDescription() { this.fullTextDescription = null; makeFullTextIndexable(); if (this.comment != null) { if (this.fullTextDescription == null) { this.fullTextDescription = ""; } this.fullTextDescription += " " + this.comment; this.fullTextDescription = this.fullTextDescription.trim(); } this.timeUpdated = DateTimeUtils.currentTimeMillis(); persistTags(); } /** Clears this instance's tags. */ public void clearTags() { if (tagSet != null) { tagSet.clear(); } tags = ""; } /** * Clears this instance's existing tags, parses the given tags {@link String} which is delimited by the given * <code>delimiter</code>, replacing illegal characters with an underscore, and adds them to this instance's * {@link #tags} and {@link #tagSet} fields. One should ALWAYS use this method instead of directly setting the * member fields. * * @see Tag#parseTags(String, char) */ public void addTags(final String tagsStr, final char delimiter) { if (tagsStr != null && tagsStr.length() > 0) { // create the Set if necessary if (tagSet == null) { tagSet = new HashSet<Tag>(); } tagSet.addAll(Tag.parseTags(tagsStr, delimiter)); // build the String representation buildTagsStringFromTagsSet(); } } /** Returns an {@link Collections#unmodifiableSet(Set) unmodifiable Set} of the tags for this facet. */ public Set<Tag> getTags() { return Collections.unmodifiableSet(tagSet); } /** Returns an {@link SortedSet} of the tags for this facet. Modifying the returned set will have no effect on the facet's tags. */ public SortedSet<String> getTagsAsStrings() { final SortedSet<String> tagStrings = new TreeSet<String>(); if ((tagSet != null) && (!tagSet.isEmpty())) { for (final Tag tag : tagSet) { if (tag != null && tag.name.length() > 0) { tagStrings.add(tag.name); } } } return tagStrings; } public boolean hasTags() { return tagSet != null && tagSet.size() > 0; } public static AbstractFacet getOldestFacet(EntityManager em, ApiKey apiKey, ObjectType objType) { return getOldestOrLatestFacet(em, apiKey, objType, "ASC"); } public static AbstractFacet getLatestFacet(EntityManager em, ApiKey apiKey, ObjectType objType){ return getOldestOrLatestFacet(em, apiKey, objType, "DESC"); } private static AbstractFacet getOldestOrLatestFacet(EntityManager em, ApiKey apiKey, ObjectType objType, String sortOrder) { Class facetClass; if (objType != null) { facetClass = objType.facetClass(); } else { facetClass = apiKey.getConnector().facetClass(); } final String entityName = JPAUtils.getEntityName(facetClass); String queryString = String.format("SELECT * FROM %s USE INDEX (apiKey) WHERE apiKeyId=? ORDER BY end %s", entityName, sortOrder); // this is a temporary hack before we bite the bullet and add the index on all tables - for now only // the location table is problematic if (entityName.equals("Facet_Location")) queryString = String.format("SELECT * FROM %s USE INDEX (apiKeyIdEnd) WHERE apiKeyId=? ORDER BY end %s", entityName, sortOrder); Query query = em.createNativeQuery(queryString, facetClass); query.setParameter(1, apiKey.getId()); query.setMaxResults(1); final List<? extends AbstractFacet> resultList = query.getResultList(); if (resultList != null && resultList.size() > 0) { return resultList.get(0); } return null; } public static List<AbstractFacet> getFacetsBefore(EntityManager em, ApiKey apiKey, ObjectType objType, Long timeInMillis, Integer desiredCount) { return getFacetsBefore(em, apiKey, objType, timeInMillis, desiredCount, null); } public static List<AbstractFacet> getFacetsAfter(EntityManager em, ApiKey apiKey, ObjectType objType, Long timeInMillis, Integer desiredCount){ return getFacetsAfter(em, apiKey, objType, timeInMillis, desiredCount, null); } public static List<AbstractFacet> getFacetsBefore(EntityManager em, ApiKey apiKey, ObjectType objType, Long timeInMillis, Integer desiredCount, @Nullable final TagFilter tagFilter) { final Class facetClass = getFacetClass(apiKey, objType); final String entityName = JPAUtils.getEntityName(facetClass); final String additionalWhereClause = (tagFilter == null) ? "" : " AND (" + tagFilter.getWhereClause() + ")"; String queryString = String.format("SELECT * FROM %s facet USE INDEX (apiKey) WHERE apiKeyId=? AND start <=? %s ORDER BY start DESC", entityName, additionalWhereClause); final Query query = em.createNativeQuery(queryString, facetClass); query.setParameter(1, apiKey.getId()); query.setParameter(2, timeInMillis); query.setMaxResults(desiredCount); return query.getResultList(); } public static List<AbstractFacet> getFacetsAfter(EntityManager em, ApiKey apiKey, ObjectType objType, Long timeInMillis, Integer desiredCount, @Nullable final TagFilter tagFilter){ final Class facetClass = getFacetClass(apiKey, objType); final String entityName = JPAUtils.getEntityName(facetClass); final String additionalWhereClause = (tagFilter == null) ? "" : " AND (" + tagFilter.getWhereClause() + ")"; String queryString = String.format("SELECT * FROM %s facet USE INDEX (apiKey) WHERE apiKeyId=? AND start >=? %s ORDER BY start ASC", entityName, additionalWhereClause); final Query query = em.createNativeQuery(queryString, facetClass); query.setParameter(1, apiKey.getId()); query.setParameter(2, timeInMillis); query.setMaxResults(desiredCount); return (List<AbstractFacet>)query.getResultList(); } private static Class getFacetClass(final ApiKey apiKey, final ObjectType objType) { final Class facetClass; if (objType != null) { facetClass = objType.facetClass(); } else { facetClass = apiKey.getConnector().facetClass(); } return facetClass; } protected abstract void makeFullTextIndexable(); }