001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.content.server.indexer;
018
019import com.echothree.model.control.content.server.control.ContentControl;
020import com.echothree.model.control.core.common.MimeTypeUsageTypes;
021import com.echothree.model.control.core.server.control.EntityInstanceControl;
022import com.echothree.model.control.index.common.IndexConstants;
023import com.echothree.model.control.index.common.IndexFieldVariations;
024import com.echothree.model.control.index.common.IndexFields;
025import com.echothree.model.control.index.server.indexer.BaseIndexer;
026import com.echothree.model.control.index.server.indexer.FieldTypes;
027import com.echothree.model.control.index.server.indexer.IndexerDebugFlags;
028import com.echothree.model.control.index.server.indexer.sortabledescriptionproducer.SortableDescriptionProducer;
029import com.echothree.model.control.index.server.indexer.sortabledescriptionproducer.SortableDescriptionProducerFactory;
030import com.echothree.model.control.item.common.ItemPriceTypes;
031import com.echothree.model.control.item.server.analyzer.ItemAnalyzer;
032import com.echothree.model.control.item.server.control.ItemControl;
033import com.echothree.model.data.content.server.entity.ContentCatalog;
034import com.echothree.model.data.content.server.entity.ContentCatalogItem;
035import com.echothree.model.data.core.server.entity.EntityInstance;
036import com.echothree.model.data.index.server.entity.Index;
037import com.echothree.model.data.item.server.entity.Item;
038import com.echothree.model.data.item.server.entity.ItemDescriptionType;
039import com.echothree.util.server.message.ExecutionErrorAccumulator;
040import com.echothree.util.server.persistence.Session;
041import java.util.List;
042import org.apache.lucene.analysis.Analyzer;
043import org.apache.lucene.document.Document;
044import org.apache.lucene.document.Field;
045import org.apache.lucene.document.LongPoint;
046import org.apache.lucene.document.SortedDocValuesField;
047import org.apache.lucene.util.BytesRef;
048
049public class ContentCatalogItemIndexer
050        extends BaseIndexer<ContentCatalogItem> {
051
052    ContentControl contentControl = Session.getModelController(ContentControl.class);
053    ItemControl itemControl = Session.getModelController(ItemControl.class);
054
055    List<ItemDescriptionType> itemDescriptionTypes;
056    SortableDescriptionProducer sortableDescriptionProducer;
057
058    /** Creates a new instance of ItemIndexer */
059    public ContentCatalogItemIndexer(final ExecutionErrorAccumulator eea, final Index index) {
060        super(eea, index);
061        
062        itemDescriptionTypes = itemControl.getItemDescriptionTypesByIncludeInIndex();
063        sortableDescriptionProducer = SortableDescriptionProducerFactory.getInstance().getSortableDescriptionProducer(index.getLastDetail().getLanguage());
064    }
065
066    @Override
067    protected Analyzer getAnalyzer() {
068        return new ItemAnalyzer(eea, language, entityType, entityAliasTypes, entityAttributes, tagScopes);
069    }
070    
071    @Override
072    protected ContentCatalogItem getEntity(final EntityInstance entityInstance) {
073        return contentControl.getContentCatalogItemByEntityInstance(entityInstance);
074    }
075
076    private void addItemToDocument(final Item item, final Document document) {
077        var itemDetail = item.getLastDetail();
078        var itemAliases = itemControl.getItemAliasesByItem(item);
079        var itemDeliveryType = itemDetail.getItemDeliveryType();
080        var itemInventoryType = itemDetail.getItemInventoryType();
081        var itemAccountingCategory = itemDetail.getItemAccountingCategory();
082        var itemPurchasingCategory = itemDetail.getItemPurchasingCategory();
083        var inventorySerialized = itemDetail.getInventorySerialized();
084        var shippingEndTime = itemDetail.getShippingEndTime();
085        var salesOrderEndTime = itemDetail.getSalesOrderEndTime();
086        var purchaseOrderStartTime = itemDetail.getPurchaseOrderStartTime();
087        var purchaseOrderEndTime = itemDetail.getPurchaseOrderEndTime();
088
089        document.add(new Field(IndexFields.itemName.name(), itemDetail.getItemName(), FieldTypes.NOT_STORED_TOKENIZED));
090        document.add(new SortedDocValuesField(IndexFields.itemName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
091                new BytesRef(itemDetail.getItemName())));
092        document.add(new Field(IndexFields.itemNameAndAliases.name(), itemDetail.getItemName(), FieldTypes.NOT_STORED_TOKENIZED));
093
094        document.add(new Field(IndexFields.itemTypeName.name(), itemDetail.getItemType().getItemTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
095        document.add(new Field(IndexFields.itemUseTypeName.name(), itemDetail.getItemUseType().getItemUseTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
096
097        if(itemDeliveryType != null) {
098            document.add(new Field(IndexFields.itemDeliveryTypeName.name(), itemDeliveryType.getItemDeliveryTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
099        }
100
101        if(itemInventoryType != null) {
102            document.add(new Field(IndexFields.itemInventoryTypeName.name(), itemInventoryType.getItemInventoryTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
103        }
104
105        itemAliases.stream().map((itemAlias) -> {
106            document.add(new Field(IndexFields.aliases.name(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
107            return itemAlias;
108        }).map((itemAlias) -> {
109            document.add(new Field(IndexFields.itemNameAndAliases.name(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
110            return itemAlias;
111        }).forEach((itemAlias) -> {
112            document.add(new Field(itemAlias.getItemAliasType().getLastDetail().getItemAliasTypeName(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
113        });
114
115        document.add(new Field(IndexFields.itemCategoryName.name(), itemDetail.getItemCategory().getLastDetail().getItemCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
116
117        if(itemAccountingCategory != null) {
118            document.add(new Field(IndexFields.itemAccountingCategoryName.name(), itemDetail.getItemAccountingCategory().getLastDetail().getItemAccountingCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
119        }
120
121        if(itemPurchasingCategory != null) {
122            document.add(new Field(IndexFields.itemPurchasingCategoryName.name(), itemDetail.getItemPurchasingCategory().getLastDetail().getItemPurchasingCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
123        }
124
125        if(inventorySerialized != null) {
126            document.add(new Field(IndexFields.inventorySerialized.name(), itemDetail.getInventorySerialized().toString(), FieldTypes.NOT_STORED_TOKENIZED));
127        }
128
129        document.add(new Field(IndexFields.shippingChargeExempt.name(), itemDetail.getShippingChargeExempt().toString(), FieldTypes.NOT_STORED_TOKENIZED));
130        document.add(new LongPoint(IndexFields.shippingStartTime.name(), itemDetail.getShippingStartTime()));
131        if(shippingEndTime != null) {
132            document.add(new LongPoint(IndexFields.shippingEndTime.name(), shippingEndTime));
133        }
134        document.add(new LongPoint(IndexFields.salesOrderStartTime.name(), itemDetail.getSalesOrderStartTime()));
135        if(salesOrderEndTime != null) {
136            document.add(new LongPoint(IndexFields.salesOrderEndTime.name(), salesOrderEndTime));
137        }
138        if(purchaseOrderStartTime != null) {
139            document.add(new LongPoint(IndexFields.purchaseOrderStartTime.name(), purchaseOrderStartTime));
140        }
141        if(purchaseOrderEndTime != null) {
142            document.add(new LongPoint(IndexFields.purchaseOrderEndTime.name(), purchaseOrderEndTime));
143        }
144        document.add(new Field(IndexFields.allowClubDiscounts.name(), itemDetail.getAllowClubDiscounts().toString(), FieldTypes.NOT_STORED_TOKENIZED));
145        document.add(new Field(IndexFields.allowCouponDiscounts.name(), itemDetail.getAllowCouponDiscounts().toString(), FieldTypes.NOT_STORED_TOKENIZED));
146        document.add(new Field(IndexFields.allowAssociatePayments.name(), itemDetail.getAllowAssociatePayments().toString(), FieldTypes.NOT_STORED_TOKENIZED));
147
148        document.add(new Field(IndexFields.unitOfMeasureKindName.name(), itemDetail.getUnitOfMeasureKind().getLastDetail().getUnitOfMeasureKindName(), FieldTypes.NOT_STORED_TOKENIZED));
149        document.add(new Field(IndexFields.itemPriceTypeName.name(), itemDetail.getItemPriceType().getItemPriceTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
150
151        itemDescriptionTypes.forEach((itemDescriptionType) -> {
152            var itemDescription = itemControl.getBestItemDescription(itemDescriptionType, item, language);
153            if (itemDescription != null) {
154                var itemDescriptionTypeDetail = itemDescriptionType.getLastDetail();
155                var mimeTypeUsageType = itemDescriptionTypeDetail.getMimeTypeUsageType();
156                var itemDescriptionTypeName = itemDescriptionTypeDetail.getItemDescriptionTypeName();
157
158                if(mimeTypeUsageType == null) {
159                    var itemStringDescription = itemControl.getItemStringDescription(itemDescription);
160                    var stringDescription = itemStringDescription.getStringDescription();
161
162                    document.add(new Field(itemDescriptionTypeName, stringDescription, FieldTypes.NOT_STORED_TOKENIZED));
163                    document.add(new Field(itemDescriptionTypeName + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.dictionary.name(),
164                            stringDescription, FieldTypes.NOT_STORED_TOKENIZED));
165                    document.add(new SortedDocValuesField(itemDescriptionTypeName + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
166                            new BytesRef(sortableDescriptionProducer.getSortableDescription(stringDescription))));
167
168                    if(IndexerDebugFlags.LogItemIndexing) {
169                        log.info("--- " + itemDescriptionTypeName + ", stringDescription = " + stringDescription);
170                    }
171                } else {
172                    var mimeTypeUsageTypeName = mimeTypeUsageType.getMimeTypeUsageTypeName();
173
174                    if(mimeTypeUsageTypeName.equals(MimeTypeUsageTypes.TEXT.name())) {
175                        var itemClobDescription = itemControl.getItemClobDescription(itemDescription);
176                        var clobDescription = itemClobDescription.getClobDescription();
177
178                        // TODO: mime type conversion to text/plain happens here
179                        document.add(new Field(itemDescriptionTypeName, clobDescription, FieldTypes.NOT_STORED_TOKENIZED));
180
181                        if(IndexerDebugFlags.LogItemIndexing) {
182                            log.info("--- " + itemDescriptionTypeName + ", clobDescription = " + clobDescription);
183                        }
184                    } // Others are not supported at this time, DOCUMENT probably should be.
185                }
186            }
187        });
188    }
189
190    private void addContentCatalogToDocument(final ContentCatalog contentCatalog, final Document document) {
191        var contentCatalogDetail = contentCatalog.getLastDetail();
192        var contentCollectionDetail = contentCatalogDetail.getContentCollection().getLastDetail();
193
194        document.add(new Field(IndexFields.contentCollectionName.name(), contentCollectionDetail.getContentCollectionName(),
195                FieldTypes.NOT_STORED_TOKENIZED));
196        document.add(new SortedDocValuesField(IndexFields.contentCollectionName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
197                new BytesRef(contentCollectionDetail.getContentCollectionName())));
198        document.add(new Field(IndexFields.contentCatalogName.name(), contentCatalogDetail.getContentCatalogName(), FieldTypes.NOT_STORED_TOKENIZED));
199        document.add(new SortedDocValuesField(IndexFields.contentCatalogName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
200                new BytesRef(contentCatalogDetail.getContentCatalogName())));
201    }
202
203    private void addContentCatalogItemToDocument(final ContentCatalogItem contentCatalogItem, final Document document) {
204        var contentControl = Session.getModelController(ContentControl.class);
205        var unitOfMeasureTypeDetail = contentCatalogItem.getUnitOfMeasureType().getLastDetail();
206
207        document.add(new Field(IndexFields.inventoryConditionName.name(), contentCatalogItem.getInventoryCondition().getLastDetail().getInventoryConditionName(), FieldTypes.NOT_STORED_TOKENIZED));
208        document.add(new Field(IndexFields.unitOfMeasureKindName.name(), unitOfMeasureTypeDetail.getUnitOfMeasureKind().getLastDetail().getUnitOfMeasureKindName(), FieldTypes.NOT_STORED_TOKENIZED));
209        document.add(new Field(IndexFields.unitOfMeasureTypeName.name(), unitOfMeasureTypeDetail.getUnitOfMeasureTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
210        document.add(new Field(IndexFields.currencyIsoName.name(), contentCatalogItem.getCurrency().getCurrencyIsoName(), FieldTypes.NOT_STORED_TOKENIZED));
211
212        var contentCategoryItems = contentControl.getContentCategoryItemsByContentCatalogItem(contentCatalogItem);
213        var contentCategoryNamesBuilder = new StringBuilder();
214        if(!contentCategoryItems.isEmpty()) {
215            contentCategoryItems.forEach((contentCategoryItem) -> {
216                if(!contentCategoryNamesBuilder.isEmpty()) {
217                    contentCategoryNamesBuilder.append(' ');
218                }
219
220                contentCategoryNamesBuilder.append(contentCategoryItem.getContentCategory().getLastDetail().getContentCategoryName());
221            });
222            document.add(new Field(IndexFields.contentCategoryNames.name(), contentCategoryNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
223        }
224
225        var itemPriceType = ItemPriceTypes.valueOf(contentCatalogItem.getItem().getLastDetail().getItemPriceType().getItemPriceTypeName());
226        switch(itemPriceType) {
227            case FIXED -> {
228                var fixedItemPrice = contentControl.getContentCatalogItemFixedPrice(contentCatalogItem);
229
230                document.add(new LongPoint(IndexFields.unitPrice.name(), fixedItemPrice.getUnitPrice()));
231            }
232            case VARIABLE -> {
233                var variableItemPrice = contentControl.getContentCatalogItemVariablePrice(contentCatalogItem);
234
235                document.add(new LongPoint(IndexFields.minimumUnitPrice.name(), variableItemPrice.getMinimumUnitPrice()));
236                document.add(new LongPoint(IndexFields.maximumUnitPrice.name(), variableItemPrice.getMaximumUnitPrice()));
237                document.add(new LongPoint(IndexFields.unitPriceIncrement.name(), variableItemPrice.getUnitPriceIncrement()));
238            }
239        }
240    }
241
242    @Override
243    protected Document convertToDocument(final EntityInstance entityInstance, final ContentCatalogItem contentCatalogItem) {
244        var entityInstanceControl = Session.getModelController(EntityInstanceControl.class);
245        var document = newDocumentWithEntityInstanceFields(entityInstance, contentCatalogItem.getPrimaryKey());
246
247        addEntityInstanceFieldsToDocument(document, entityInstanceControl.getEntityInstanceByBasePK(contentCatalogItem.getItemPK()));
248        addEntityInstanceFieldsToDocument(document, entityInstanceControl.getEntityInstanceByBasePK(contentCatalogItem.getContentCatalogPK()));
249
250        addItemToDocument(contentCatalogItem.getItem(), document);
251        addContentCatalogToDocument(contentCatalogItem.getContentCatalog(), document);
252        addContentCatalogItemToDocument(contentCatalogItem, document);
253
254        return document;
255    }
256
257}