svn commit: r1531499 - in /ofbiz/trunk/specialpurpose: ecommerce/webapp/ecommerce/WEB-INF/actions/content/ lucene/src/org/ofbiz/content/search/ lucene/src/org/ofbiz/content/test/ lucene/webapp/content/WEB-INF/ lucene/webapp/content/WEB-INF/actions/ luc...

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1531499 - in /ofbiz/trunk/specialpurpose: ecommerce/webapp/ecommerce/WEB-INF/actions/content/ lucene/src/org/ofbiz/content/search/ lucene/src/org/ofbiz/content/test/ lucene/webapp/content/WEB-INF/ lucene/webapp/content/WEB-INF/actions/ luc...

jacopoc
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>