/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.ofbiz.content.content; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilDateTime; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.common.KeywordSearchUtil; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.condition.EntityConditionList; import org.apache.ofbiz.entity.condition.EntityExpr; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.model.DynamicViewEntity; import org.apache.ofbiz.entity.model.ModelKeyMap; import org.apache.ofbiz.entity.model.ModelViewEntity.ComplexAlias; import org.apache.ofbiz.entity.model.ModelViewEntity.ComplexAliasField; import org.apache.ofbiz.entity.transaction.GenericTransactionException; import org.apache.ofbiz.entity.transaction.TransactionUtil; import org.apache.ofbiz.entity.util.EntityListIterator; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtil; public class ContentSearch { public static final String module = ContentSearch.class.getName(); public static final String resource = "ContentUiLabels"; public static ArrayList<String> searchContents(List<? extends ContentSearchConstraint> contentSearchConstraintList, ResultSortOrder resultSortOrder, Delegator delegator, String visitId) { ContentSearchContext contentSearchContext = new ContentSearchContext(delegator, visitId); contentSearchContext.addContentSearchConstraints(contentSearchConstraintList); contentSearchContext.setResultSortOrder(resultSortOrder); ArrayList<String> contentIds = contentSearchContext.doSearch(); return contentIds; } public static void getAllSubContentIds(String contentId, Set<String> contentIdSet, Delegator delegator, Timestamp nowTimestamp) { if (nowTimestamp == null) { nowTimestamp = UtilDateTime.nowTimestamp(); } // first make sure the current id is in the Set contentIdSet.add(contentId); // now find all sub-categories, filtered by effective dates, and call this routine for them try { List<GenericValue> contentAssocList = EntityQuery.use(delegator).from("ContentAssoc").where("contentIdFrom", contentId).cache().queryList(); for (GenericValue contentAssoc: contentAssocList) { String subContentId = contentAssoc.getString("contentIdTo"); if (contentIdSet.contains(subContentId)) { // if this category has already been traversed, no use doing it again; this will also avoid infinite loops continue; } // do the date filtering in the loop to avoid looping through the list twice if (EntityUtil.isValueActive(contentAssoc, nowTimestamp)) { getAllSubContentIds(subContentId, contentIdSet, delegator, nowTimestamp); } } // Find Content where current contentId = contentParentId; only select minimal fields to keep the size low List<GenericValue> childContentList = EntityQuery.use(delegator) .select("contentId", "contentParentId").from("Content") .where("contentParentId", contentId) .cache().queryList(); for (GenericValue childContent: childContentList) { String subContentId = childContent.getString("contentId"); if (contentIdSet.contains(subContentId)) { // if this category has already been traversed, no use doing it again; this will also avoid infinite loops continue; } // do the date filtering in the loop to avoid looping through the list twice getAllSubContentIds(subContentId, contentIdSet, delegator, nowTimestamp); } } catch (GenericEntityException e) { Debug.logError(e, "Error finding sub-categories for content search", module); } } public static class ContentSearchContext { public int index = 1; public List<EntityCondition> entityConditionList = new LinkedList<EntityCondition>(); public List<String> orderByList = new LinkedList<String>(); public Set<String> fieldsToSelect = UtilMisc.toSet("contentId"); public DynamicViewEntity dynamicViewEntity = new DynamicViewEntity(); public boolean contentIdGroupBy = false; public boolean includedKeywordSearch = false; public Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); public List<Set<String>> keywordFixedOrSetAndList = new LinkedList<Set<String>>(); public Set<String> orKeywordFixedSet = new HashSet<String>(); public Set<String> andKeywordFixedSet = new HashSet<String>(); public List<GenericValue> contentSearchConstraintList = new LinkedList<GenericValue>(); public ResultSortOrder resultSortOrder = null; public Integer resultOffset = null; public Integer maxResults = null; protected Delegator delegator = null; protected String visitId = null; protected Integer totalResults = null; public ContentSearchContext(Delegator delegator, String visitId) { this.delegator = delegator; this.visitId = visitId; dynamicViewEntity.addMemberEntity("CNT", "Content"); } public Delegator getDelegator() { return this.delegator; } public void addContentSearchConstraints(List<? extends ContentSearchConstraint> contentSearchConstraintList) { // Go through the constraints and add them in for (ContentSearchConstraint constraint: contentSearchConstraintList) { constraint.addConstraint(this); } } public void setResultSortOrder(ResultSortOrder resultSortOrder) { this.resultSortOrder = resultSortOrder; } public void setResultOffset(Integer resultOffset) { this.resultOffset = resultOffset; } public void setMaxResults(Integer maxResults) { this.maxResults = maxResults; } public Integer getTotalResults() { return this.totalResults; } public ArrayList<String> doSearch() { long startMillis = System.currentTimeMillis(); // do the query EntityListIterator eli = this.doQuery(delegator); ArrayList<String> contentIds = this.makeContentIdList(eli); if (eli != null) { try { eli.close(); } catch (GenericEntityException e) { Debug.logError(e, "Error closing ContentSearch EntityListIterator"); } } long endMillis = System.currentTimeMillis(); double totalSeconds = ((double)endMillis - (double)startMillis)/1000.0; // store info about results in the database, attached to the user's visitId, if specified this.saveSearchResultInfo(Long.valueOf(contentIds.size()), Double.valueOf(totalSeconds)); return contentIds; } public void finishKeywordConstraints() { if (orKeywordFixedSet.size() == 0 && andKeywordFixedSet.size() == 0 && keywordFixedOrSetAndList.size() == 0) { return; } // we know we have a keyword search to do, so keep track of that now... this.includedKeywordSearch = true; // if there is anything in the orKeywordFixedSet add it to the keywordFixedOrSetAndList if (orKeywordFixedSet.size() > 0) { // put in keywordFixedOrSetAndList to process with other or lists where at least one is required keywordFixedOrSetAndList.add(orKeywordFixedSet); } // remove all or sets from the or set and list where the or set is size 1 and put them in the and list Iterator<Set<String>> keywordFixedOrSetAndTestIter = keywordFixedOrSetAndList.iterator(); while (keywordFixedOrSetAndTestIter.hasNext()) { Set<String> keywordFixedOrSet = keywordFixedOrSetAndTestIter.next(); if (keywordFixedOrSet.size() == 0) { keywordFixedOrSetAndTestIter.remove(); } else if (keywordFixedOrSet.size() == 1) { // treat it as just another and andKeywordFixedSet.add(keywordFixedOrSet.iterator().next()); keywordFixedOrSetAndTestIter.remove(); } } boolean doingBothAndOr = (keywordFixedOrSetAndList.size() > 1) || (keywordFixedOrSetAndList.size() > 0 && andKeywordFixedSet.size() > 0); Debug.logInfo("Finished initial setup of keywords, doingBothAndOr=" + doingBothAndOr + ", andKeywordFixedSet=" + andKeywordFixedSet + "\n keywordFixedOrSetAndList=" + keywordFixedOrSetAndList, module); ComplexAlias relevancyComplexAlias = new ComplexAlias("+"); if (andKeywordFixedSet.size() > 0) { // add up the relevancyWeight fields from all keyword member entities for a total to sort by for (String keyword: andKeywordFixedSet) { // make index based values and increment String entityAlias = "PK" + index; String prefix = "pk" + index; index++; dynamicViewEntity.addMemberEntity(entityAlias, "ContentKeyword"); dynamicViewEntity.addAlias(entityAlias, prefix + "Keyword", "keyword", null, null, null, null); dynamicViewEntity.addViewLink("CNT", entityAlias, Boolean.FALSE, ModelKeyMap.makeKeyMapList("contentId")); entityConditionList.add(EntityCondition.makeCondition(prefix + "Keyword", EntityOperator.LIKE, keyword)); //don't add an alias for this, will be part of a complex alias: dynamicViewEntity.addAlias(entityAlias, prefix + "RelevancyWeight", "relevancyWeight", null, null, null, null); relevancyComplexAlias.addComplexAliasMember(new ComplexAliasField(entityAlias, "relevancyWeight", null, null)); } //TODO: find out why Oracle and other dbs don't like the query resulting from this and fix: contentIdGroupBy = true; if (!doingBothAndOr) { dynamicViewEntity.addAlias(null, "totalRelevancy", null, null, null, null, null, relevancyComplexAlias); } } if (keywordFixedOrSetAndList.size() > 0) { for (Set<String> keywordFixedOrSet: keywordFixedOrSetAndList) { // make index based values and increment String entityAlias = "PK" + index; String prefix = "pk" + index; index++; dynamicViewEntity.addMemberEntity(entityAlias, "ContentKeyword"); dynamicViewEntity.addAlias(entityAlias, prefix + "Keyword", "keyword", null, null, null, null); dynamicViewEntity.addViewLink("CNT", entityAlias, Boolean.FALSE, ModelKeyMap.makeKeyMapList("contentId")); List<EntityExpr> keywordOrList = new LinkedList<EntityExpr>(); for (String keyword: keywordFixedOrSet) { keywordOrList.add(EntityCondition.makeCondition(prefix + "Keyword", EntityOperator.LIKE, keyword)); } entityConditionList.add(EntityCondition.makeCondition(keywordOrList, EntityOperator.OR)); contentIdGroupBy = true; if (doingBothAndOr) { relevancyComplexAlias.addComplexAliasMember(new ComplexAliasField(entityAlias, "relevancyWeight", null, "sum")); } else { dynamicViewEntity.addAlias(entityAlias, "totalRelevancy", "relevancyWeight", null, null, null, "sum"); } } } if (doingBothAndOr) { dynamicViewEntity.addAlias(null, "totalRelevancy", null, null, null, null, null, relevancyComplexAlias); } } public EntityListIterator doQuery(Delegator delegator) { // handle the now assembled or and and keyword fixed lists this.finishKeywordConstraints(); if (resultSortOrder != null) { resultSortOrder.setSortOrder(this); } dynamicViewEntity.addAlias("CNT", "contentId", null, null, null, Boolean.valueOf(contentIdGroupBy), null); EntityCondition whereCondition = EntityCondition.makeCondition(entityConditionList, EntityOperator.AND); EntityListIterator eli = null; try { eli = EntityQuery.use(delegator) .select(fieldsToSelect).from(dynamicViewEntity) .where(whereCondition) .cursorScrollInsensitive() .distinct() .maxRows(maxResults) .queryIterator(); } catch (GenericEntityException e) { Debug.logError(e, "Error in content search", module); return null; } return eli; } public ArrayList<String> makeContentIdList(EntityListIterator eli) { ArrayList<String> contentIds = new ArrayList<String>(maxResults == null ? 100 : maxResults.intValue()); if (eli == null) { Debug.logWarning("The eli is null, returning zero results", module); return contentIds; } try { boolean hasResults = false; Object initialResult = null; initialResult = eli.next(); if (initialResult != null) { hasResults = true; } if (resultOffset != null && resultOffset.intValue() > 1) { if (Debug.infoOn()) Debug.logInfo("Before relative, current index=" + eli.currentIndex(), module); hasResults = eli.relative(resultOffset.intValue() - 1); initialResult = null; } // get the first as the current one GenericValue searchResult = null; if (hasResults) { if (initialResult != null) { searchResult = (GenericValue) initialResult; } else { searchResult = eli.currentGenericValue(); } } if (searchResult == null) { // nothing to get... int failTotal = 0; if (this.resultOffset != null) { failTotal = this.resultOffset.intValue() - 1; } this.totalResults = Integer.valueOf(failTotal); return contentIds; } // init numRetreived to one since we have already grabbed the initial one int numRetreived = 1; int duplicatesFound = 0; Set<String> contentIdSet = new HashSet<String>(); contentIds.add(searchResult.getString("contentId")); contentIdSet.add(searchResult.getString("contentId")); while (((searchResult = eli.next()) != null) && (maxResults == null || numRetreived < maxResults.intValue())) { String contentId = searchResult.getString("contentId"); if (!contentIdSet.contains(contentId)) { contentIds.add(contentId); contentIdSet.add(contentId); numRetreived++; } else { duplicatesFound++; } } if (searchResult != null) { this.totalResults = eli.getResultsSizeAfterPartialList(); } if (this.totalResults == null || this.totalResults.intValue() == 0) { int total = numRetreived; if (this.resultOffset != null) { total += (this.resultOffset.intValue() - 1); } this.totalResults = Integer.valueOf(total); } Debug.logInfo("Got search values, numRetreived=" + numRetreived + ", totalResults=" + totalResults + ", maxResults=" + maxResults + ", resultOffset=" + resultOffset + ", duplicatesFound(in the current results)=" + duplicatesFound, module); } catch (GenericEntityException e) { Debug.logError(e, "Error getting results from the content search query", module); } return contentIds; } public void saveSearchResultInfo(Long numResults, Double secondsTotal) { // uses entities: ContentSearchResult and ContentSearchConstraint try { // make sure this is in a transaction boolean beganTransaction = TransactionUtil.begin(); try { GenericValue contentSearchResult = delegator.makeValue("ContentSearchResult"); String contentSearchResultId = delegator.getNextSeqId("ContentSearchResult"); contentSearchResult.set("contentSearchResultId", contentSearchResultId); contentSearchResult.set("visitId", this.visitId); if (this.resultSortOrder != null) { contentSearchResult.set("orderByName", this.resultSortOrder.getOrderName()); contentSearchResult.set("isAscending", this.resultSortOrder.isAscending() ? "Y" : "N"); } contentSearchResult.set("numResults", numResults); contentSearchResult.set("secondsTotal", secondsTotal); contentSearchResult.set("searchDate", nowTimestamp); contentSearchResult.create(); int seqId = 1; for (GenericValue contentSearchConstraint: contentSearchConstraintList) { contentSearchConstraint.set("contentSearchResultId", contentSearchResultId); contentSearchConstraint.set("constraintSeqId", Integer.toString(seqId)); contentSearchConstraint.create(); seqId++; } TransactionUtil.commit(beganTransaction); } catch (GenericEntityException e1) { String errMsg = "Error saving content search result info/stats"; Debug.logError(e1, errMsg, module); TransactionUtil.rollback(beganTransaction, errMsg, e1); } } catch (GenericTransactionException e) { Debug.logError(e, "Error saving content search result info/stats", module); } } } // ====================================================================== // Search Constraint Classes // ====================================================================== @SuppressWarnings("serial") public static abstract class ContentSearchConstraint implements java.io.Serializable { public ContentSearchConstraint() { } public abstract void addConstraint(ContentSearchContext contentSearchContext); /** pretty print for log messages and even UI stuff */ public abstract String prettyPrintConstraint(Delegator delegator, boolean detailed, Locale locale); } @SuppressWarnings("serial") public static class ContentAssocConstraint extends ContentSearchConstraint { public static final String constraintName = "ContentAssoc"; protected String contentId; protected String contentAssocTypeId; protected boolean includeSubContents; public ContentAssocConstraint(String contentId, String contentAssocTypeId, boolean includeSubContents) { this.contentId = contentId; this.contentAssocTypeId = contentAssocTypeId; this.includeSubContents = includeSubContents; } @Override public void addConstraint(ContentSearchContext contentSearchContext) { Set<String> contentIdSet = new HashSet<String>(); if (includeSubContents) { // find all sub-categories recursively, make a Set of contentId ContentSearch.getAllSubContentIds(contentId, contentIdSet, contentSearchContext.getDelegator(), contentSearchContext.nowTimestamp); } else { contentIdSet.add(contentId); } // allow assoc from or to the current WE and the contentId on this constraint // make index based values and increment String entityAlias; String prefix; // do contentId = contentIdFrom, contentIdTo IN contentIdSet entityAlias = "CNT" + contentSearchContext.index; prefix = "cnt" + contentSearchContext.index; contentSearchContext.index++; contentSearchContext.dynamicViewEntity.addMemberEntity(entityAlias, "ContentAssoc"); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentIdFrom", "contentIdFrom", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentIdTo", "contentIdTo", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentAssocTypeId", "contentAssocTypeId", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "FromDate", "fromDate", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ThruDate", "thruDate", null, null, null, null); contentSearchContext.dynamicViewEntity.addViewLink("CNT", entityAlias, Boolean.TRUE, ModelKeyMap.makeKeyMapList("contentId","contentIdFrom")); List<EntityExpr> assocConditionFromTo = new LinkedList<EntityExpr>(); assocConditionFromTo.add(EntityCondition.makeCondition(prefix + "ContentIdTo", EntityOperator.IN, contentIdSet)); if (UtilValidate.isNotEmpty(contentAssocTypeId)) { assocConditionFromTo.add(EntityCondition.makeCondition(prefix + "ContentAssocTypeId", EntityOperator.EQUALS, contentAssocTypeId)); } assocConditionFromTo.add(EntityCondition.makeCondition(EntityCondition.makeCondition(prefix + "ThruDate", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition(prefix + "ThruDate", EntityOperator.GREATER_THAN, contentSearchContext.nowTimestamp))); assocConditionFromTo.add(EntityCondition.makeCondition(prefix + "FromDate", EntityOperator.LESS_THAN, contentSearchContext.nowTimestamp)); // do contentId = contentIdTo, contentIdFrom IN contentIdSet entityAlias = "CNT" + contentSearchContext.index; prefix = "cnt" + contentSearchContext.index; contentSearchContext.index++; contentSearchContext.dynamicViewEntity.addMemberEntity(entityAlias, "ContentAssoc"); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentIdFrom", "contentIdFrom", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentIdTo", "contentIdTo", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ContentAssocTypeId", "contentAssocTypeId", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "FromDate", "fromDate", null, null, null, null); contentSearchContext.dynamicViewEntity.addAlias(entityAlias, prefix + "ThruDate", "thruDate", null, null, null, null); contentSearchContext.dynamicViewEntity.addViewLink("CNT", entityAlias, Boolean.TRUE, ModelKeyMap.makeKeyMapList("contentId","contentIdTo")); List<EntityExpr> assocConditionToFrom = new LinkedList<EntityExpr>(); assocConditionToFrom.add(EntityCondition.makeCondition(prefix + "ContentIdFrom", EntityOperator.IN, contentIdSet)); if (UtilValidate.isNotEmpty(contentAssocTypeId)) { assocConditionToFrom.add(EntityCondition.makeCondition(prefix + "ContentAssocTypeId", EntityOperator.EQUALS, contentAssocTypeId)); } assocConditionToFrom.add(EntityCondition.makeCondition(EntityCondition.makeCondition(prefix + "ThruDate", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition(prefix + "ThruDate", EntityOperator.GREATER_THAN, contentSearchContext.nowTimestamp))); assocConditionToFrom.add(EntityCondition.makeCondition(prefix + "FromDate", EntityOperator.LESS_THAN, contentSearchContext.nowTimestamp)); // now create and add the combined constraint contentSearchContext.entityConditionList.add(EntityCondition.makeCondition(EntityCondition.makeCondition(assocConditionFromTo, EntityOperator.AND), EntityOperator.OR, EntityCondition.makeCondition(assocConditionToFrom, EntityOperator.AND))); // add in contentSearchConstraint, don't worry about the contentSearchResultId or constraintSeqId, those will be fill in later contentSearchContext.contentSearchConstraintList.add(contentSearchContext.getDelegator().makeValue("ContentSearchConstraint", UtilMisc.toMap("constraintName", constraintName, "infoString", this.contentId + "," + this.contentAssocTypeId, "includeSubContents", this.includeSubContents ? "Y" : "N"))); } /** pretty print for log messages and even UI stuff */ @Override public String prettyPrintConstraint(Delegator delegator, boolean detailed, Locale locale) { GenericValue content = null; GenericValue contentAssocType = null; try { content = EntityQuery.use(delegator).from("Content").where("contentId", this.contentId).cache().queryOne(); contentAssocType = EntityQuery.use(delegator).from("ContentAssocType").where("contentAssocTypeId", this.contentAssocTypeId).cache().queryOne(); } catch (GenericEntityException e) { Debug.logError(e, "Error looking up ContentAssocConstraint pretty print info: " + e.toString(), module); } StringBuilder ppBuf = new StringBuilder(); ppBuf.append(UtilProperties.getMessage(resource, "ContentAssoc", locale) + ": "); if (content != null) { ppBuf.append(content.getString("contentName")); } if (content == null || detailed) { ppBuf.append(" ["); ppBuf.append(contentId); ppBuf.append("]"); } if (UtilValidate.isNotEmpty(this.contentAssocTypeId)) { if (contentAssocType != null) { ppBuf.append(contentAssocType.getString("description")); } if (contentAssocType == null || detailed) { ppBuf.append(" ["); ppBuf.append(contentAssocTypeId); ppBuf.append("]"); } } if (this.includeSubContents) { ppBuf.append(" (").append(UtilProperties.getMessage(resource, "ContentIncludeAllSubContents", locale)).append(")"); } return ppBuf.toString(); } @Override public boolean equals(Object obj) { ContentSearchConstraint psc = (ContentSearchConstraint) obj; if (psc instanceof ContentAssocConstraint) { ContentAssocConstraint that = (ContentAssocConstraint) psc; if (this.includeSubContents != that.includeSubContents) { return false; } if (this.contentId == null) { if (that.contentId != null) { return false; } } else { if (!this.contentId.equals(that.contentId)) { return false; } } if (this.contentAssocTypeId == null) { if (that.contentAssocTypeId != null) { return false; } } else { if (!this.contentAssocTypeId.equals(that.contentAssocTypeId)) { return false; } } return true; } else { return false; } } } @SuppressWarnings("serial") public static class KeywordConstraint extends ContentSearchConstraint { public static final String constraintName = "Keyword"; protected String keywordsString; protected boolean anyPrefix; protected boolean anySuffix; protected boolean isAnd; protected boolean removeStems; public KeywordConstraint(String keywordsString, boolean anyPrefix, boolean anySuffix, Boolean removeStems, boolean isAnd) { this.keywordsString = keywordsString; this.anyPrefix = anyPrefix; this.anySuffix = anySuffix; this.isAnd = isAnd; if (removeStems != null) { this.removeStems = removeStems.booleanValue(); } else { this.removeStems = UtilProperties.propertyValueEquals("keywordsearch", "remove.stems", "true"); } } public Set<String> makeFullKeywordSet(Delegator delegator) { Set<String> keywordSet = KeywordSearchUtil.makeKeywordSet(this.keywordsString, null, true); Set<String> fullKeywordSet = new TreeSet<String>(); // expand the keyword list according to the thesaurus and create a new set of keywords for (String keyword: keywordSet) { Set<String> expandedSet = new TreeSet<String>(); boolean replaceEntered = KeywordSearchUtil.expandKeywordForSearch(keyword, expandedSet, delegator); fullKeywordSet.addAll(expandedSet); if (!replaceEntered) { fullKeywordSet.add(keyword); } } return fullKeywordSet; } @Override public void addConstraint(ContentSearchContext contentSearchContext) { // just make the fixed keyword lists and put them in the context if (isAnd) { // when isAnd is true we need to make a list of keyword sets where each set corresponds to one //incoming/entered keyword and contains all of the expanded keywords plus the entered keyword if none of //the expanded keywords are flagged as replacements; now the tricky part: each set should be or'ed together, //but then the sets should be and'ed to produce the overall expression; create the SQL for this //needs some work as the current method only support a list of and'ed words and a list of or'ed words, not //a list of or'ed sets to be and'ed together Set<String> keywordSet = KeywordSearchUtil.makeKeywordSet(this.keywordsString, null, true); // expand the keyword list according to the thesaurus and create a new set of keywords for (String keyword: keywordSet) { Set<String> expandedSet = new TreeSet<String>(); boolean replaceEntered = KeywordSearchUtil.expandKeywordForSearch(keyword, expandedSet, contentSearchContext.getDelegator()); if (!replaceEntered) { expandedSet.add(keyword); } Set<String> fixedSet = KeywordSearchUtil.fixKeywordsForSearch(expandedSet, anyPrefix, anySuffix, removeStems, isAnd); Set<String> fixedKeywordSet = new HashSet<String>(); fixedKeywordSet.addAll(fixedSet); contentSearchContext.keywordFixedOrSetAndList.add(fixedKeywordSet); } } else { // when isAnd is false, just add all of the new entries to the big list Set<String> keywordFirstPass = makeFullKeywordSet(contentSearchContext.getDelegator()); // includes keyword expansion, etc Set<String> keywordSet = KeywordSearchUtil.fixKeywordsForSearch(keywordFirstPass, anyPrefix, anySuffix, removeStems, isAnd); contentSearchContext.orKeywordFixedSet.addAll(keywordSet); } // add in contentSearchConstraint, don't worry about the contentSearchResultId or constraintSeqId, those will be fill in later Map<String, String> valueMap = UtilMisc.toMap("constraintName", constraintName, "infoString", this.keywordsString); valueMap.put("anyPrefix", this.anyPrefix ? "Y" : "N"); valueMap.put("anySuffix", this.anySuffix ? "Y" : "N"); valueMap.put("isAnd", this.isAnd ? "Y" : "N"); valueMap.put("removeStems", this.removeStems ? "Y" : "N"); contentSearchContext.contentSearchConstraintList.add(contentSearchContext.getDelegator().makeValue("ContentSearchConstraint", valueMap)); } /** pretty print for log messages and even UI stuff */ @Override public String prettyPrintConstraint(Delegator delegator, boolean detailed, Locale locale) { StringBuilder ppBuf = new StringBuilder(); ppBuf.append(UtilProperties.getMessage(resource, "ContentKeywords", locale)).append(": \""); ppBuf.append(this.keywordsString).append("\", ").append(UtilProperties.getMessage(resource, "ContentKeywordWhere", locale)).append(" "); ppBuf.append(isAnd ? UtilProperties.getMessage(resource, "ContentKeywordAllWordsMatch", locale) : UtilProperties.getMessage(resource, "ContentKeywordAnyWordMatches", locale)); return ppBuf.toString(); } @Override public boolean equals(Object obj) { ContentSearchConstraint psc = (ContentSearchConstraint) obj; if (psc instanceof KeywordConstraint) { KeywordConstraint that = (KeywordConstraint) psc; if (this.anyPrefix != that.anyPrefix) { return false; } if (this.anySuffix != that.anySuffix) { return false; } if (this.isAnd != that.isAnd) { return false; } if (this.removeStems != that.removeStems) { return false; } if (this.keywordsString == null) { if (that.keywordsString != null) { return false; } } else { if (!this.keywordsString.equals(that.keywordsString)) { return false; } } return true; } else { return false; } } } @SuppressWarnings("serial") public static class LastUpdatedRangeConstraint extends ContentSearchConstraint { public static final String constraintName = "LastUpdatedRange"; protected Timestamp fromDate; protected Timestamp thruDate; public LastUpdatedRangeConstraint(Timestamp fromDate, Timestamp thruDate) { this.fromDate = fromDate; this.thruDate = thruDate; } @Override public void addConstraint(ContentSearchContext contentSearchContext) { contentSearchContext.dynamicViewEntity.addAlias("CNT", "lastModifiedDate", "lastModifiedDate", null, null, null, null); EntityConditionList<EntityExpr> dateConditions = null; EntityExpr dateCondition=null; if (fromDate !=null && thruDate!=null) { dateConditions= EntityCondition.makeCondition(UtilMisc.toList( EntityCondition.makeCondition("lastModifiedDate", EntityOperator.GREATER_THAN_EQUAL_TO, fromDate), EntityCondition.makeCondition("lastModifiedDate", EntityOperator.LESS_THAN_EQUAL_TO, thruDate)), EntityOperator.AND); } if (fromDate !=null) { dateCondition=EntityCondition.makeCondition("lastModifiedDate", EntityOperator.GREATER_THAN_EQUAL_TO, fromDate); } else if (thruDate != null) { dateCondition = EntityCondition.makeCondition("lastModifiedDate", EntityOperator.LESS_THAN_EQUAL_TO, thruDate); } EntityConditionList<? extends EntityCondition> conditions = null; if (fromDate !=null && thruDate!=null) { conditions=EntityCondition.makeCondition(UtilMisc.toList( dateConditions, EntityCondition.makeCondition("lastModifiedDate", EntityOperator.EQUALS, null)), EntityOperator.OR); } else { conditions=EntityCondition.makeCondition(UtilMisc.toList( dateCondition, EntityCondition.makeCondition("lastModifiedDate", EntityOperator.EQUALS, null)), EntityOperator.OR); } contentSearchContext.entityConditionList.add(conditions); // add in contentSearchConstraint, don't worry about the contentSearchResultId or constraintSeqId, those will be fill in later contentSearchContext.contentSearchConstraintList.add(contentSearchContext.getDelegator().makeValue("ContentSearchConstraint", UtilMisc.toMap("constraintName", constraintName, "infoString","fromDate : " + fromDate + " thruDate : " + thruDate))); } /** pretty print for log messages and even UI stuff */ @Override public String prettyPrintConstraint(Delegator delegator, boolean detailed, Locale locale) { StringBuilder ppBuf = new StringBuilder(); ppBuf.append(UtilProperties.getMessage(resource, "ContentLastModified", locale)).append(": \""); ppBuf.append(fromDate).append("-").append(thruDate).append("\", ").append(UtilProperties.getMessage(resource, "ContentLastModified", locale)).append(" "); return ppBuf.toString(); } @Override public boolean equals(Object obj) { ContentSearchConstraint psc = (ContentSearchConstraint) obj; if (psc instanceof LastUpdatedRangeConstraint) { LastUpdatedRangeConstraint that = (LastUpdatedRangeConstraint) psc; if (this.fromDate == null) { if (that.fromDate != null) { return false; } } else { if (!this.fromDate.equals(that.fromDate)) { return false; } } if (this.thruDate == null) { if (that.thruDate != null) { return false; } } else { if (!this.thruDate.equals(that.thruDate)) { return false; } } return true; } else { return false; } } } // ====================================================================== // Result Sort Classes // ====================================================================== @SuppressWarnings("serial") public static abstract class ResultSortOrder implements java.io.Serializable { public ResultSortOrder() { } public abstract void setSortOrder(ContentSearchContext contentSearchContext); public abstract String getOrderName(); public abstract String prettyPrintSortOrder(boolean detailed, Locale locale); public abstract boolean isAscending(); } @SuppressWarnings("serial") public static class SortKeywordRelevancy extends ResultSortOrder { public SortKeywordRelevancy() { } @Override public void setSortOrder(ContentSearchContext contentSearchContext) { if (contentSearchContext.includedKeywordSearch) { // we have to check this in order to be sure that there is a totalRelevancy to sort by... contentSearchContext.orderByList.add("-totalRelevancy"); contentSearchContext.fieldsToSelect.add("totalRelevancy"); } } @Override public String getOrderName() { return "KeywordRelevancy"; } @Override public String prettyPrintSortOrder(boolean detailed, Locale locale) { return UtilProperties.getMessage(resource, "ContentKeywordRelevancy", locale); } @Override public boolean isAscending() { return false; } } @SuppressWarnings("serial") public static class SortContentField extends ResultSortOrder { protected String fieldName; protected boolean ascending; /** Some good field names to try might include: * [contentName] * [totalQuantityOrdered] for most popular or most purchased * [lastModifiedDate] * * You can also include any other field on the Content entity. */ public SortContentField(String fieldName, boolean ascending) { this.fieldName = fieldName; this.ascending = ascending; } @Override public void setSortOrder(ContentSearchContext contentSearchContext) { if (contentSearchContext.getDelegator().getModelEntity("Content").isField(fieldName)) { contentSearchContext.dynamicViewEntity.addAlias("CNT", fieldName); } if (ascending) { contentSearchContext.orderByList.add("+" + fieldName); } else { contentSearchContext.orderByList.add("-" + fieldName); } contentSearchContext.fieldsToSelect.add(fieldName); } @Override public String getOrderName() { return "ContentField:" + this.fieldName; } @Override public String prettyPrintSortOrder(boolean detailed, Locale locale) { if ("contentName".equals(this.fieldName)) { return UtilProperties.getMessage(resource, "ContentName", locale); } else if ("totalQuantityOrdered".equals(this.fieldName)) { return UtilProperties.getMessage(resource, "ContentPopularityByOrders", locale); } else if ("totalTimesViewed".equals(this.fieldName)) { return UtilProperties.getMessage(resource, "ContentPopularityByViews", locale); } else if ("averageCustomerRating".equals(this.fieldName)) { return UtilProperties.getMessage(resource, "ContentCustomerRating", locale); } return this.fieldName; } @Override public boolean isAscending() { return this.ascending; } } }