Author: jacopoc
Date: Sat Oct 12 04:52:17 2013 New Revision: 1531499 URL: http://svn.apache.org/r1531499 Log: Expanded and enhanced the "lucene" specialpurpose component by implementing an initial version of a Product indexer; also implemented a screen to test and analyze Lucene queries on the index. Added: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java (with props) ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy (with props) ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy (with props) Modified: ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/content/Search.groovy ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchServices.java ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchWorker.java ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/test/LuceneTests.java ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/Search.groovy ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/controller.xml ofbiz/trunk/specialpurpose/lucene/widget/LuceneForms.xml ofbiz/trunk/specialpurpose/lucene/widget/LuceneMenus.xml ofbiz/trunk/specialpurpose/lucene/widget/LuceneScreens.xml Modified: ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/content/Search.groovy URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/content/Search.groovy?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/content/Search.groovy (original) +++ ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/actions/content/Search.groovy Sat Oct 12 04:52:17 2013 @@ -52,7 +52,7 @@ featureIdByType = ParametricSearch.makeF //Debug.logInfo("in search, featureIdByType:" + featureIdByType, ""); combQuery = new BooleanQuery(); -Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath(null))); +Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath("content"))); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher searcher = null; Analyzer analyzer = null; Added: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java?rev=1531499&view=auto ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java (added) +++ ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java Sat Oct 12 04:52:17 2013 @@ -0,0 +1,552 @@ +/******************************************************************************* + * 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.ofbiz.content.search; + +import java.io.File; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.lucene.document.IntField; +import org.apache.lucene.document.LongField; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.GeneralException; +import org.ofbiz.base.util.UtilDateTime; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.content.data.DataResourceWorker; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.condition.EntityCondition; +import org.ofbiz.entity.condition.EntityOperator; +import org.ofbiz.entity.util.EntityUtil; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.DoubleField; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.Term; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.LockObtainFailedException; + +public class ProductIndexer extends Thread { + + public static final String module = ProductIndexer.class.getName(); + + private static Map<Delegator, ProductIndexer> productIndexerMap = new HashMap<Delegator, ProductIndexer>(); + private LinkedBlockingQueue<String> productIndexQueue = new LinkedBlockingQueue<String>(); + private Delegator delegator; + private Directory indexDirectory; + private IndexWriterConfig indexWriterConfiguration; + private static final String NULL_STRING = "NULL"; + // TODO: Move to property file + private static final int UNCOMMITTED_DOC_LIMIT = 100; + + private ProductIndexer(Delegator delegator) { + this.delegator = delegator; + Analyzer analyzer = new StandardAnalyzer(SearchWorker.LUCENE_VERSION); + this.indexWriterConfiguration = new IndexWriterConfig(SearchWorker.LUCENE_VERSION, analyzer); + try { + this.indexDirectory = FSDirectory.open(new File(SearchWorker.getIndexPath("products"))); + } catch (CorruptIndexException e) { + Debug.logError("Corrupted lucene index: " + e.getMessage(), module); + } catch (LockObtainFailedException e) { + Debug.logError("Could not obtain Lock on lucene index " + e.getMessage(), module); + } catch (IOException e) { + Debug.logError(e.getMessage(), module); + } + } + + public static synchronized ProductIndexer getInstance(Delegator delegator) { + ProductIndexer productIndexer = productIndexerMap.get(delegator); + if (productIndexer == null) { + productIndexer = new ProductIndexer(delegator); + productIndexer.setName("ProductIndexer_" + delegator.getDelegatorName()); + productIndexer.start(); + productIndexerMap.put(delegator, productIndexer); + } + return productIndexer; + } + + @Override + public void run() { + IndexWriter indexWriter = null; + int uncommittedDocs = 0; + while (true) { + String productId; + try { + // Execution will pause here until the queue receives a product for indexing + productId = productIndexQueue.take(); + } catch (InterruptedException e) { + Debug.logError(e, module); + if (indexWriter != null) { + try { + indexWriter.close(); + indexWriter = null; + } catch(IOException ioe) { + Debug.logError(ioe, module); + } + } + break; + } + Document productDocument = this.prepareProductDocument(productId); + Term documentIdentifier = new Term("productId", productId); + if (indexWriter == null) { + try { + indexWriter = new IndexWriter(this.indexDirectory, this.indexWriterConfiguration); + } catch (CorruptIndexException e) { + Debug.logError("Corrupted lucene index: " + e.getMessage(), module); + break; + } catch (LockObtainFailedException e) { + Debug.logError("Could not obtain Lock on lucene index " + e.getMessage(), module); + // TODO: put the thread to sleep waiting for the locked to be released + break; + } catch (IOException e) { + Debug.logError(e.getMessage(), module); + break; + } + } + try { + if (productDocument == null) { + indexWriter.deleteDocuments(documentIdentifier); + if (Debug.infoOn()) Debug.logInfo("Deleted Lucene document for product: " + productId, module); + } else { + indexWriter.updateDocument(documentIdentifier, productDocument); + if (Debug.infoOn()) Debug.logInfo("Indexed Lucene document for product: " + productId, module); + } + } catch(Exception e) { + Debug.logError(e, "Error processing Lucene document for product: " + productId, module); + if (productIndexQueue.peek() == null) { + try { + indexWriter.close(); + indexWriter = null; + } catch(IOException ioe) { + Debug.logError(ioe, module); + } + } + continue; + } + uncommittedDocs++; + if (uncommittedDocs == UNCOMMITTED_DOC_LIMIT || productIndexQueue.peek() == null) { + // limit reached or queue empty, time to commit + try { + indexWriter.commit(); + } catch (IOException e) { + Debug.logError(e, module); + } + uncommittedDocs = 0; + } + if (productIndexQueue.peek() == null) { + try { + indexWriter.close(); + indexWriter = null; + } catch (IOException e) { + Debug.logError(e, module); + } + } + } + } + + public boolean queue(String productId) { + return productIndexQueue.add(productId); + } + + private Document prepareProductDocument(String productId) { + try { + GenericValue product = delegator.findOne("Product", true, "productId", productId); + if (product == null) { + // Return a null document (we will remove the document from the index) + return null; + } else { + if ("Y".equals(product.getString("isVariant")) && "true".equals(UtilProperties.getPropertyValue("prodsearch", "index.ignore.variants"))) { + return null; + } + Document doc = new Document(); + Timestamp nextReIndex = null; + + // Product Fields + doc.add(new StringField("productId", productId, Store.YES)); + this.addTextFieldByWeight(doc, "productName", product.getString("productName"), "index.weight.Product.productName", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "internalName", product.getString("internalName"), "index.weight.Product.internalName", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "brandName", product.getString("brandName"), "index.weight.Product.brandName", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "description", product.getString("description"), "index.weight.Product.description", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "longDescription", product.getString("longDescription"), "index.weight.Product.longDescription", 0, false, "fullText"); + //doc.add(new StringField("introductionDate", checkValue(product.getString("introductionDate")), Store.NO)); + doc.add(new LongField("introductionDate", quantizeTimestampToDays(product.getTimestamp("introductionDate")), Store.NO)); + nextReIndex = this.checkSetNextReIndex(product.getTimestamp("introductionDate"), nextReIndex); + doc.add(new LongField("salesDiscontinuationDate", quantizeTimestampToDays(product.getTimestamp("salesDiscontinuationDate")), Store.NO)); + nextReIndex = this.checkSetNextReIndex(product.getTimestamp("salesDiscontinuationDate"), nextReIndex); + doc.add(new StringField("isVariant", product.get("isVariant") != null && product.getBoolean("isVariant") ? "true" : "false", Store.NO)); + + // ProductFeature Fields, check that at least one of the fields is set to be indexed + if (!"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductFeatureAndAppl.description", "0")) || + !"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductFeatureAndAppl.abbrev", "0")) || + !"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductFeatureAndAppl.idCode", "0"))) { + + List<GenericValue> productFeatureAndAppls = delegator.findByAnd("ProductFeatureAndAppl", UtilMisc.toMap("productId", productId), null, false); + productFeatureAndAppls = this.filterByThruDate(productFeatureAndAppls); + + for (GenericValue productFeatureAndAppl: productFeatureAndAppls) { + Timestamp fromDate = productFeatureAndAppl.getTimestamp("fromDate"); + Timestamp thruDate = productFeatureAndAppl.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + doc.add(new StringField("productFeatureId", productFeatureAndAppl.getString("productFeatureId"), Store.NO)); + doc.add(new StringField("productFeatureCategoryId", productFeatureAndAppl.getString("productFeatureCategoryId"), Store.NO)); + doc.add(new StringField("productFeatureTypeId", productFeatureAndAppl.getString("productFeatureTypeId"), Store.NO)); + this.addTextFieldByWeight(doc, "featureDescription", productFeatureAndAppl.getString("description"), "index.weight.ProductFeatureAndAppl.description", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "featureAbbreviation", productFeatureAndAppl.getString("abbrev"), "index.weight.ProductFeatureAndAppl.abbrev", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "featureCode", productFeatureAndAppl.getString("idCode"), "index.weight.ProductFeatureAndAppl.idCode", 0, false, "fullText"); + // Get the ProductFeatureGroupIds + List<GenericValue> productFeatureGroupAppls = delegator.findByAnd("ProductFeatureGroupAppl", UtilMisc.toMap("productFeatureId", productFeatureAndAppl.get("productFeatureId")), null, false); + productFeatureGroupAppls = this.filterByThruDate(productFeatureGroupAppls); + for (GenericValue productFeatureGroupAppl : productFeatureGroupAppls) { + fromDate = productFeatureGroupAppl.getTimestamp("fromDate"); + thruDate = productFeatureGroupAppl.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + doc.add(new StringField("productFeatureGroupId", productFeatureGroupAppl.getString("productFeatureGroupId"), Store.NO)); + } + } + } + + // ProductAttribute Fields + if (!"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductAttribute.attrName", "0")) || + !"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductAttribute.attrValue", "0"))) { + + List<GenericValue> productAttributes = delegator.findByAnd("ProductAttribute", UtilMisc.toMap("productId", productId), null, false); + for (GenericValue productAttribute: productAttributes) { + this.addTextFieldByWeight(doc, "attributeName", productAttribute.getString("attrName"), "index.weight.ProductAttribute.attrName", 0, false, "fullText"); + this.addTextFieldByWeight(doc, "attributeValue", productAttribute.getString("attrValue"), "index.weight.ProductAttribute.attrValue", 0, false, "fullText"); + } + } + + // GoodIdentification + if (!"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.GoodIdentification.idValue", "0"))) { + List<GenericValue> goodIdentifications = delegator.findByAnd("GoodIdentification", UtilMisc.toMap("productId", productId), null, false); + for (GenericValue goodIdentification: goodIdentifications) { + String goodIdentificationTypeId = goodIdentification.getString("goodIdentificationTypeId"); + String idValue = goodIdentification.getString("idValue"); + doc.add(new StringField("goodIdentificationTypeId", goodIdentificationTypeId, Store.NO)); + doc.add(new StringField("goodIdentificationIdValue", idValue, Store.NO)); + doc.add(new StringField(goodIdentificationTypeId + "_GoodIdentification", idValue, Store.NO)); + this.addTextFieldByWeight(doc, "identificationValue", idValue, "index.weight.GoodIdentification.idValue", 0, false, "fullText"); + } + } + + // Virtual ProductIds + if ("Y".equals(product.getString("isVirtual"))) { + if (!"0".equals(UtilProperties.getPropertyValue("prodsearch", "index.weight.Variant.Product.productId", "0"))) { + List<GenericValue> variantProductAssocs = delegator.findByAnd("ProductAssoc", UtilMisc.toMap("productId", productId, "productAssocTypeId", "PRODUCT_VARIANT"), null, false); + variantProductAssocs = this.filterByThruDate(variantProductAssocs); + for (GenericValue variantProductAssoc: variantProductAssocs) { + Timestamp fromDate = variantProductAssoc.getTimestamp("fromDate"); + Timestamp thruDate = variantProductAssoc.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + this.addTextFieldByWeight(doc, "variantProductId", variantProductAssoc.getString("productIdTo"), "index.weight.Variant.Product.productId", 0, false, "fullText"); + } + } + } + + // Index product content + String productContentTypes = UtilProperties.getPropertyValue("prodsearch", "index.include.ProductContentTypes"); + for (String productContentTypeId: productContentTypes.split(",")) { + int weight = 1; + try { + // this is defaulting to a weight of 1 because you specified you wanted to index this type + weight = Integer.parseInt(UtilProperties.getPropertyValue("prodsearch", "index.weight.ProductContent." + productContentTypeId, "1")); + } catch (Exception e) { + Debug.logWarning("Could not parse weight number: " + e.toString(), module); + } + + List<GenericValue> productContentAndInfos = delegator.findByAnd("ProductContentAndInfo", UtilMisc.toMap("productId", productId, "productContentTypeId", productContentTypeId), null, false); + productContentAndInfos = this.filterByThruDate(productContentAndInfos); + for (GenericValue productContentAndInfo: productContentAndInfos) { + Timestamp fromDate = productContentAndInfo.getTimestamp("fromDate"); + Timestamp thruDate = productContentAndInfo.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + try { + Map<String, Object> drContext = UtilMisc.<String, Object>toMap("product", product); + String contentText = DataResourceWorker.renderDataResourceAsText(delegator, productContentAndInfo.getString("dataResourceId"), drContext, null, null, false); + this.addTextFieldByWeight(doc, "content", contentText, null, weight, false, "fullText"); + } catch (IOException e1) { + Debug.logError(e1, "Error getting content text to index", module); + } catch (GeneralException e1) { + Debug.logError(e1, "Error getting content text to index", module); + } + + // TODO: Not indexing alternate locales, needs special handling + /* + List<GenericValue> alternateViews = productContentAndInfo.getRelated("ContentAssocDataResourceViewTo", UtilMisc.toMap("caContentAssocTypeId", "ALTERNATE_LOCALE"), UtilMisc.toList("-caFromDate")); + alternateViews = EntityUtil.filterByDate(alternateViews, UtilDateTime.nowTimestamp(), "caFromDate", "caThruDate", true); + for (GenericValue thisView: alternateViews) { + } + */ + } + } + + // Index the product's directProductCategoryIds (direct parents), productCategoryIds (all ancestors) and prodCatalogIds + this.populateCategoryData(doc, product); + + // Index ProductPrices, uses dynamic fields in the format ${productPriceTypeId}_${productPricePurposeId}_${currencyUomId}_${productStoreGroupId}_price + List<GenericValue> productPrices = product.getRelated("ProductPrice", null, null, false); + productPrices = this.filterByThruDate(productPrices); + for (GenericValue productPrice : productPrices) { + Timestamp fromDate = productPrice.getTimestamp("fromDate"); + Timestamp thruDate = productPrice.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + StringBuilder fieldNameSb = new StringBuilder(); + fieldNameSb.append(productPrice.getString("productPriceTypeId")); + fieldNameSb.append('_'); + fieldNameSb.append(productPrice.getString("productPricePurposeId")); + fieldNameSb.append('_'); + fieldNameSb.append(productPrice.getString("currencyUomId")); + fieldNameSb.append('_'); + fieldNameSb.append(productPrice.getString("productStoreGroupId")); + fieldNameSb.append("_price"); + doc.add(new DoubleField(fieldNameSb.toString(), productPrice.getDouble("price"), Store.NO)); + } + + // Index ProductSuppliers + List<GenericValue> supplierProducts = product.getRelated("SupplierProduct", null, null, false); + supplierProducts = this.filterByThruDate(supplierProducts, "availableThruDate"); + Set<String> supplierPartyIds = new TreeSet<String>(); + for (GenericValue supplierProduct : supplierProducts) { + Timestamp fromDate = supplierProduct.getTimestamp("availableFromDate"); + Timestamp thruDate = supplierProduct.getTimestamp("availableThruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + supplierPartyIds.add(supplierProduct.getString("partyId")); + } + for (String supplierPartyId : supplierPartyIds) { + doc.add(new StringField("supplierPartyId", supplierPartyId, Store.NO)); + } + + // TODO: Add the nextReIndex timestamp to the document for when the product should be automatically re-indexed outside of any ECAs + // based on the next known from/thru date whose passing will cause a change to the document. Need to build a scheduled service to look for these. + return doc; + } + } catch (GenericEntityException e) { + Debug.logError(e, module); + } + return null; + } + + // An attempt to boost/weight values in a similar manner to what OFBiz product search does. + private void addTextFieldByWeight(Document doc, String fieldName, String value, String property, int defaultWeight, boolean store, String fullTextFieldName) { + if (fieldName == null) return; + + float weight = 0; + if (property != null) { + try { + weight = Float.parseFloat(UtilProperties.getPropertyValue("prodsearch", property, "0")); + } catch (Exception e) { + Debug.logWarning("Could not parse weight number: " + e.toString(), module); + } + } else if (defaultWeight > 0) { + weight = defaultWeight; + } + if (weight == 0 && !store) { + return; + } + Field field = new TextField(fieldName, checkValue(value), (store? Store.YES: Store.NO)); + if (weight > 0 && weight != 1) { + field.setBoost(weight); + } + doc.add(field); + if (fullTextFieldName != null) { + doc.add(new TextField(fullTextFieldName, checkValue(value), Store.NO)); + } + } + + private String checkValue(String value) { + if (UtilValidate.isEmpty(value)) { + return NULL_STRING; + } + return value; + } + + private Timestamp checkSetNextReIndex(Timestamp nextValue, Timestamp currentValue) { + // nextValue is null, stick with what we've got + if (nextValue == null) return currentValue; + // currentValue is null so use nextValue + if (currentValue == null) return nextValue; + // currentValue is after nextValue so use nextValue + if (currentValue.after(nextValue)) return nextValue; + // stick with current value + return currentValue; + } + + private static final EntityCondition THRU_DATE_ONLY_CONDITION = EntityCondition.makeCondition( + EntityCondition.makeCondition("thruDate", EntityOperator.EQUALS, null), + EntityOperator.OR, + EntityCondition.makeCondition("thruDate", EntityOperator.GREATER_THAN, UtilDateTime.nowTimestamp()) + ); + + private List<GenericValue> filterByThruDate(List<GenericValue> values) { + return EntityUtil.filterByCondition(values, THRU_DATE_ONLY_CONDITION); + } + + private List<GenericValue> filterByThruDate(List<GenericValue> values, String thruDateName) { + return EntityUtil.filterByCondition(values, EntityCondition.makeCondition( + EntityCondition.makeCondition(thruDateName, EntityOperator.EQUALS, null), + EntityOperator.OR, + EntityCondition.makeCondition(thruDateName, EntityOperator.GREATER_THAN, UtilDateTime.nowTimestamp()) + )); + } + + private Timestamp populateCategoryData(Document doc, GenericValue product) throws GenericEntityException { + Timestamp nextReIndex = null; + Set<String> indexedCategoryIds = new TreeSet<String>(); + List<GenericValue> productCategoryMembers = product.getRelated("ProductCategoryMember", null, null, false); + productCategoryMembers = this.filterByThruDate(productCategoryMembers); + + for (GenericValue productCategoryMember: productCategoryMembers) { + String productCategoryId = productCategoryMember.getString("productCategoryId"); + doc.add(new StringField("productCategoryId", productCategoryId, Store.NO)); + doc.add(new StringField("directProductCategoryId", productCategoryId, Store.NO)); + indexedCategoryIds.add(productCategoryId); + Timestamp fromDate = productCategoryMember.getTimestamp("fromDate"); + Timestamp thruDate = productCategoryMember.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index the feature + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + nextReIndex = this.checkSetNextReIndex( + this.getParentCategories(doc, productCategoryMember.getRelatedOne("ProductCategory", false), indexedCategoryIds), + nextReIndex); + } + return nextReIndex; + } + + private Timestamp getParentCategories(Document doc, GenericValue productCategory, Set<String> indexedCategoryIds) throws GenericEntityException { + return this.getParentCategories(doc, productCategory, indexedCategoryIds, new TreeSet<String>()); + } + + private Timestamp getParentCategories(Document doc, GenericValue productCategory, Set<String> indexedCategoryIds, Set<String> indexedCatalogIds) throws GenericEntityException { + Timestamp nextReIndex = null; + nextReIndex = this.getCategoryCatalogs(doc, productCategory, indexedCatalogIds); + List<GenericValue> productCategoryRollups = productCategory.getRelated("CurrentProductCategoryRollup", null, null, false); + productCategoryRollups = this.filterByThruDate(productCategoryRollups); + for (GenericValue productCategoryRollup : productCategoryRollups) { + Timestamp fromDate = productCategoryRollup.getTimestamp("fromDate"); + Timestamp thruDate = productCategoryRollup.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index now + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + // Skip if we've done this category already + if (!indexedCategoryIds.add(productCategoryRollup.getString("parentProductCategoryId"))) { + continue; + } + GenericValue parentProductCategory = productCategoryRollup.getRelatedOne("ParentProductCategory", false); + doc.add(new StringField("productCategoryId", parentProductCategory.getString("productCategoryId"), Store.NO)); + nextReIndex = this.checkSetNextReIndex( + this.getParentCategories(doc, parentProductCategory, indexedCategoryIds), + nextReIndex + ); + } + return nextReIndex; + } + + private Timestamp getCategoryCatalogs(Document doc, GenericValue productCategory, Set<String> indexedCatalogIds) throws GenericEntityException { + Timestamp nextReIndex = null; + List<GenericValue> prodCatalogCategories = productCategory.getRelated("ProdCatalogCategory", null, null, false); + prodCatalogCategories = this.filterByThruDate(prodCatalogCategories); + for (GenericValue prodCatalogCategory : prodCatalogCategories) { + Timestamp fromDate = prodCatalogCategory.getTimestamp("fromDate"); + Timestamp thruDate = prodCatalogCategory.getTimestamp("thruDate"); + if (fromDate != null && fromDate.after(UtilDateTime.nowTimestamp())) { + // fromDate is after now, update reindex date but don't index now + nextReIndex = this.checkSetNextReIndex(fromDate, nextReIndex); + continue; + } else if (thruDate != null) { + nextReIndex = this.checkSetNextReIndex(thruDate, nextReIndex); + } + // Skip if we've done this catalog already + if (!indexedCatalogIds.add(prodCatalogCategory.getString("prodCatalogId"))) { + continue; + } + doc.add(new StringField("prodCatalogId", prodCatalogCategory.getString("prodCatalogId"), Store.NO)); + } + return nextReIndex; + } + + private long quantizeTimestampToDays(Timestamp date) { + long quantizedDate = 0; + if (date != null) { + quantizedDate = date.getTime()/24/3600; + } + return quantizedDate; + } +} Propchange: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/ProductIndexer.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchServices.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchServices.java?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchServices.java (original) +++ ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchServices.java Sat Oct 12 04:52:17 2013 @@ -48,11 +48,7 @@ public class SearchServices { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); String siteId = (String) context.get("contentId"); - String path = (String) context.get("path"); Locale locale = (Locale) context.get("locale"); - if (path == null) { - path = SearchWorker.getIndexPath(path); - } Map<String, Object> envContext = new HashMap<String, Object>(); if (Debug.infoOn()) Debug.logInfo("in indexTree, siteId:" + siteId, module); @@ -62,7 +58,7 @@ public class SearchServices { Map<String, Object> results; try { - results = SearchWorker.indexTree(dispatcher, delegator, siteId, envContext, path); + results = SearchWorker.indexTree(dispatcher, delegator, siteId, envContext); } catch (Exception e) { Debug.logError(e, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, Modified: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchWorker.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchWorker.java?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchWorker.java (original) +++ ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/search/SearchWorker.java Sat Oct 12 04:52:17 2013 @@ -59,7 +59,7 @@ public class SearchWorker { public static final Version LUCENE_VERSION = Version.LUCENE_44; - public static Map<String, Object> indexTree(LocalDispatcher dispatcher, Delegator delegator, String siteId, Map<String, Object> context, String path) throws Exception { + public static Map<String, Object> indexTree(LocalDispatcher dispatcher, Delegator delegator, String siteId, Map<String, Object> context) throws Exception { GenericValue content = delegator.makeValue("Content", UtilMisc.toMap("contentId", siteId)); if (Debug.infoOn()) Debug.logInfo("in indexTree, siteId:" + siteId + " content:" + content, module); List<GenericValue> siteList = ContentWorker.getAssociatedContent(content, "To", UtilMisc.toList("SUBSITE", "PUBLISH_LINK", "SUB_CONTENT"), null, UtilDateTime.nowTimestamp().toString(), null); @@ -74,8 +74,8 @@ public class SearchWorker { for (GenericValue subContent : subContentList) { contentIdList.add(subContent.getString("contentId")); } - indexContentList(dispatcher, delegator, context, contentIdList, null); - indexTree(dispatcher, delegator, siteContentId, context, path); + indexContentList(dispatcher, delegator, context, contentIdList); + indexTree(dispatcher, delegator, siteContentId, context); } else { List<String> badIndexList = UtilGenerics.checkList(context.get("badIndexList")); badIndexList.add(siteContentId + " had no sub-entities."); @@ -90,11 +90,8 @@ public class SearchWorker { } public static String getIndexPath(String path) { - String indexAllPath = path; - if (UtilValidate.isEmpty(indexAllPath)) { - indexAllPath = UtilProperties.getPropertyValue("search", "defaultIndex", "index"); - } - return indexAllPath; + String basePath = UtilProperties.getPropertyValue("search", "defaultIndex", "index"); + return (UtilValidate.isNotEmpty(path)? basePath + "/" + path: basePath); } private static IndexWriter getDefaultIndexWriter(Directory directory) { @@ -117,8 +114,8 @@ public class SearchWorker { return writer; } - public static void indexContentList(LocalDispatcher dispatcher, Delegator delegator, Map<String, Object> context,List<String> idList, String path) throws Exception { - Directory directory = FSDirectory.open(new File(getIndexPath(path))); + public static void indexContentList(LocalDispatcher dispatcher, Delegator delegator, Map<String, Object> context,List<String> idList) throws Exception { + Directory directory = FSDirectory.open(new File(getIndexPath("content"))); if (Debug.infoOn()) Debug.logInfo("in indexContentList, indexAllPath: " + directory.toString(), module); // Delete existing documents IndexWriter writer = getDefaultIndexWriter(directory); Modified: ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/test/LuceneTests.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/test/LuceneTests.java?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/test/LuceneTests.java (original) +++ ofbiz/trunk/specialpurpose/lucene/src/org/ofbiz/content/test/LuceneTests.java Sat Oct 12 04:52:17 2013 @@ -73,7 +73,7 @@ public class LuceneTests extends OFBizTe } public void testSearchTermHand() throws Exception { - Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath(null))); + Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath("content"))); DirectoryReader r = null; try { r = DirectoryReader.open(directory); Added: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy?rev=1531499&view=auto ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy (added) +++ ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy Sat Oct 12 04:52:17 2013 @@ -0,0 +1,49 @@ +/* + * 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. + */ + +import org.ofbiz.content.search.ProductIndexer +import org.ofbiz.entity.transaction.TransactionUtil +import org.ofbiz.entity.util.EntityListIterator + +ProductIndexer pi = ProductIndexer.getInstance(delegator) +if (pi) { + productsCounter = 0 + beganTransaction = TransactionUtil.begin() + EntityListIterator products + try { + products = delegator.find('Product', null, null, new TreeSet(['productId']), null, null) + while (product = products.next()) { + pi.queue(product.productId) + productsCounter++ + } + } catch(Exception e) { + TransactionUtil.rollback(beganTransaction, e.getMessage(), e) + return error(e.getMessage()) + } finally { + if (products != null) { + try { + products.close() + } catch (Exception exc) {} + } + TransactionUtil.commit(beganTransaction) + } + return success("Submitted for indexing $productsCounter products") +} else { + return error() +} \ No newline at end of file Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/Search.groovy URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/Search.groovy?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/Search.groovy (original) +++ ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/Search.groovy Sat Oct 12 04:52:17 2013 @@ -45,7 +45,7 @@ featureIdByType = ParametricSearch.makeF Debug.logInfo("in search, featureIdByType:" + featureIdByType, ""); combQuery = new BooleanQuery(); -Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath(null))); +Directory directory = FSDirectory.open(new File(SearchWorker.getIndexPath("content"))); DirectoryReader reader = DirectoryReader.open(directory); try { Added: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy?rev=1531499&view=auto ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy (added) +++ ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy Sat Oct 12 04:52:17 2013 @@ -0,0 +1,72 @@ +/* + * 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. + */ + + +import org.apache.lucene.analysis.core.WhitespaceAnalyzer +import org.ofbiz.content.search.SearchWorker + +import org.apache.lucene.document.Document +import org.apache.lucene.index.DirectoryReader +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser +import org.apache.lucene.search.BooleanClause +import org.apache.lucene.search.BooleanQuery +import org.apache.lucene.search.IndexSearcher +import org.apache.lucene.search.Query +import org.apache.lucene.search.ScoreDoc +import org.apache.lucene.search.TopScoreDocCollector +import org.apache.lucene.store.FSDirectory + +if (parameters.luceneQuery) { + Query combQuery = new BooleanQuery(); + IndexSearcher searcher; + WhitespaceAnalyzer analyzer; + try { + DirectoryReader reader = DirectoryReader.open(FSDirectory.open(new File(SearchWorker.getIndexPath("products")))); + searcher = new IndexSearcher(reader); + analyzer = new WhitespaceAnalyzer(SearchWorker.LUCENE_VERSION); + } catch (FileNotFoundException e) { + context.errorMessageList.add(e.getMessage()); + return; + } + + QueryParser parser = new QueryParser(SearchWorker.LUCENE_VERSION, "fullText", analyzer); + parser.setLocale(locale); + Query query; + try { + query = parser.parse(parameters.luceneQuery); + } catch(ParseException pe) { + context.errorMessageList.add(pe.getMessage()); + return; + } + combQuery.add(query, BooleanClause.Occur.MUST); + + TopScoreDocCollector collector = TopScoreDocCollector.create(100, false); // defaulting to 100 results + searcher.search(combQuery, collector); + ScoreDoc[] hits = collector.topDocs().scoreDocs; + + productList = [] + hits.each { hit -> + Document doc = searcher.doc(hit.doc) + productId = doc.productId + product = delegator.findOne("Product", [productId : productId], true) + productList.add(product) + } + context.queryResults = productList; +} Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy ------------------------------------------------------------------------------ svn:eol-style = native Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy ------------------------------------------------------------------------------ svn:keywords = Date Rev Author URL Id Propchange: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/controller.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/controller.xml?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/controller.xml (original) +++ ofbiz/trunk/specialpurpose/lucene/webapp/content/WEB-INF/controller.xml Sat Oct 12 04:52:17 2013 @@ -26,6 +26,8 @@ under the License. <description>Lucene Component Site Configuration File</description> + <handler name="groovy" type="request" class="org.ofbiz.webapp.event.GroovyEventHandler"/> + <!-- Request Mappings --> <request-map uri="AdminSearch"><security https="true" auth="true"/><response name="success" type="view" value="AdminSearch"/></request-map> <request-map uri="AdminIndex"><security https="true" auth="true"/><response name="success" type="view" value="AdminIndex"/></request-map> @@ -35,8 +37,22 @@ under the License. <response name="success" type="view" value="AdminIndex"/> <response name="error" type="view" value="AdminIndex"/> </request-map> - + + <request-map uri="indexProducts"> + <security https="true" auth="true"/> + <event path="component://lucene/webapp/content/WEB-INF/actions/IndexProducts.groovy" type="groovy"/> + <response name="success" type="view" value="AdminIndex"/> + <response name="error" type="view" value="AdminIndex"/> + </request-map> + + <request-map uri="ProductSearch"> + <security https="true" auth="true"/> + <response name="success" type="view" value="ProductSearch"/> + <response name="error" type="view" value="ProductSearch"/> + </request-map> + <!-- View Mappings --> + <view-map name="ProductSearch" page="component://lucene/widget/LuceneScreens.xml#ProductSearch" type="screen"/> <view-map name="AdminSearch" page="component://lucene/widget/LuceneScreens.xml#AdminSearch" type="screen"/> <view-map name="AdminIndex" page="component://lucene/widget/LuceneScreens.xml#AdminIndex" type="screen"/> </site-conf> \ No newline at end of file Modified: ofbiz/trunk/specialpurpose/lucene/widget/LuceneForms.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/widget/LuceneForms.xml?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/widget/LuceneForms.xml (original) +++ ofbiz/trunk/specialpurpose/lucene/widget/LuceneForms.xml Sat Oct 12 04:52:17 2013 @@ -20,14 +20,13 @@ under the License. <forms xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/widget-form.xsd"> - <form name="AdminIndex" target="AdminIndex" title="" type="single" header-row-style="header-row" default-table-style="basic-table"> - <field name="indexContentIds" position="1"><text size="50"/></field> - <field name="submitButton" title="${uiLabelMap.ContentIndexEnteredIds}" widget-style="smallSubmit" position="2"><submit button-type="button"/></field> + <form name="IndexContentDocuments" target="indexTree" title="" type="single" header-row-style="header-row" default-table-style="basic-table"> + <field name="submitButton" title="${uiLabelMap.ContentIndexAll}" widget-style="smallSubmit" position="1"><submit button-type="button"/></field> + <field name="contentId" position="2"><text size="50" default-value="WebStoreCONTENT"/></field> </form> - <form name="AdminIndexAll" target="indexTree" title="" type="single" header-row-style="header-row" default-table-style="basic-table"> - <field name="contentId" title=" " position="1"><text size="50" default-value="WebStoreCONTENT"/></field> - <field name="submitButton" title="${uiLabelMap.ContentIndexAll}" widget-style="smallSubmit" position="2"><submit button-type="button"/></field> + <form name="IndexProductDocuments" target="indexProducts" title="" type="single" header-row-style="header-row" default-table-style="basic-table"> + <field name="submitButton" title="Index Products" widget-style="smallSubmit"><submit button-type="button"/></field> </form> <form name="searchList" type="list" target="" list-name="queryResults" paginate-target="/AdminSearch" @@ -44,4 +43,17 @@ under the License. <field name="contentName"><display/></field> </form> + <form name="ProductList" type="list" target="" list-name="queryResults" paginate-target="" + odd-row-style="alternate-row" default-table-style="basic-table hover-bar"> + <field name="productId"><display/></field> + <field name="description"><display/></field> + <field name="longDescription"><display/></field> + + </form> + + <form name="LuceneQuery" target="ProductSearch" title="" type="single" header-row-style="header-row" default-table-style="basic-table"> + <field name="luceneQuery"><text size="50"/></field> + <field name="submitButton" widget-style="smallSubmit" ><submit button-type="button"/></field> + </form> + </forms> \ No newline at end of file Modified: ofbiz/trunk/specialpurpose/lucene/widget/LuceneMenus.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/widget/LuceneMenus.xml?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/widget/LuceneMenus.xml (original) +++ ofbiz/trunk/specialpurpose/lucene/widget/LuceneMenus.xml Sat Oct 12 04:52:17 2013 @@ -41,6 +41,11 @@ under the License. <menu-item name="search" title="${uiLabelMap.CommonFind}" > <link id="search" target="AdminSearch"/> </menu-item> + + <menu-item name="productSearch" title="Search Products" > + <link id="productSearch" target="ProductSearch"/> + </menu-item> + </menu> </menus> \ No newline at end of file Modified: ofbiz/trunk/specialpurpose/lucene/widget/LuceneScreens.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/lucene/widget/LuceneScreens.xml?rev=1531499&r1=1531498&r2=1531499&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/lucene/widget/LuceneScreens.xml (original) +++ ofbiz/trunk/specialpurpose/lucene/widget/LuceneScreens.xml Sat Oct 12 04:52:17 2013 @@ -81,6 +81,28 @@ under the License. </section> </screen> + <screen name="ProductSearch"> + <section> + <actions> + <script location="component://content/webapp/content/WEB-INF/actions/cms/GetMenuContext.groovy"/> + <script location="component://lucene/webapp/content/WEB-INF/actions/SearchProducts.groovy"/> + <set field="titleProperty" value="ContentCMSSearchPage"/> + <set field="currentCMSMenuItemName" value="productSearch" to-scope="user"/> + <property-map map-name="uiLabelMap" resource="ProductUiLabels"/> + </actions> + <widgets> + <decorator-screen name="commonCmsDecorator"> + <decorator-section name="body"> + <screenlet title="${uiLabelMap.ContentCMSSearchPage}"> + <include-form name="LuceneQuery" location="component://lucene/widget/LuceneForms.xml"/> + <include-form name="ProductList" location="component://lucene/widget/LuceneForms.xml"/> + </screenlet> + </decorator-section> + </decorator-screen> + </widgets> + </section> + </screen> + <screen name="AdminIndex"> <section> <actions> @@ -92,8 +114,8 @@ under the License. <decorator-screen name="commonCmsDecorator"> <decorator-section name="body"> <screenlet title="${uiLabelMap.ContentCMSSearchPage}"> - <include-form name="AdminIndex" location="component://lucene/widget/LuceneForms.xml"/> - <include-form name="AdminIndexAll" location="component://lucene/widget/LuceneForms.xml"/> + <include-form name="IndexContentDocuments" location="component://lucene/widget/LuceneForms.xml"/> + <include-form name="IndexProductDocuments" location="component://lucene/widget/LuceneForms.xml"/> </screenlet> </decorator-section> </decorator-screen> |
Free forum by Nabble | Edit this page |