001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 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 javax.enterprise.context.Dependent;
043import javax.inject.Inject;
044import org.apache.lucene.analysis.Analyzer;
045import org.apache.lucene.document.Document;
046import org.apache.lucene.document.Field;
047import org.apache.lucene.document.LongPoint;
048import org.apache.lucene.document.SortedDocValuesField;
049import org.apache.lucene.util.BytesRef;
050
051@Dependent
052public class ContentCatalogItemIndexer
053        extends BaseIndexer<ContentCatalogItem> {
054
055    @Inject
056    ContentControl contentControl;
057
058    @Inject
059    ItemControl itemControl;
060
061    List<ItemDescriptionType> itemDescriptionTypes;
062    SortableDescriptionProducer sortableDescriptionProducer;
063
064    @Override
065    public BaseIndexer<ContentCatalogItem> setup(final ExecutionErrorAccumulator eea, final Index index) {
066        itemDescriptionTypes = itemControl.getItemDescriptionTypesByIncludeInIndex();
067        sortableDescriptionProducer = SortableDescriptionProducerFactory.getInstance().getSortableDescriptionProducer(index.getLastDetail().getLanguage());
068
069        return super.setup(eea, index);
070    }
071
072    @Override
073    protected Analyzer getAnalyzer() {
074        return new ItemAnalyzer(eea, language, entityType, entityAliasTypes, entityAttributes, tagScopes);
075    }
076    
077    @Override
078    protected ContentCatalogItem getEntity(final EntityInstance entityInstance) {
079        return contentControl.getContentCatalogItemByEntityInstance(entityInstance);
080    }
081
082    private void addItemToDocument(final Item item, final Document document) {
083        var itemDetail = item.getLastDetail();
084        var itemAliases = itemControl.getItemAliasesByItem(item);
085        var itemDeliveryType = itemDetail.getItemDeliveryType();
086        var itemInventoryType = itemDetail.getItemInventoryType();
087        var itemAccountingCategory = itemDetail.getItemAccountingCategory();
088        var itemPurchasingCategory = itemDetail.getItemPurchasingCategory();
089        var inventorySerialized = itemDetail.getInventorySerialized();
090        var shippingEndTime = itemDetail.getShippingEndTime();
091        var salesOrderEndTime = itemDetail.getSalesOrderEndTime();
092        var purchaseOrderStartTime = itemDetail.getPurchaseOrderStartTime();
093        var purchaseOrderEndTime = itemDetail.getPurchaseOrderEndTime();
094
095        document.add(new Field(IndexFields.itemName.name(), itemDetail.getItemName(), FieldTypes.NOT_STORED_TOKENIZED));
096        document.add(new SortedDocValuesField(IndexFields.itemName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
097                new BytesRef(itemDetail.getItemName())));
098        document.add(new Field(IndexFields.itemNameAndAliases.name(), itemDetail.getItemName(), FieldTypes.NOT_STORED_TOKENIZED));
099
100        document.add(new Field(IndexFields.itemTypeName.name(), itemDetail.getItemType().getItemTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
101        document.add(new Field(IndexFields.itemUseTypeName.name(), itemDetail.getItemUseType().getItemUseTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
102
103        if(itemDeliveryType != null) {
104            document.add(new Field(IndexFields.itemDeliveryTypeName.name(), itemDeliveryType.getItemDeliveryTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
105        }
106
107        if(itemInventoryType != null) {
108            document.add(new Field(IndexFields.itemInventoryTypeName.name(), itemInventoryType.getItemInventoryTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
109        }
110
111        itemAliases.stream().map((itemAlias) -> {
112            document.add(new Field(IndexFields.aliases.name(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
113            return itemAlias;
114        }).map((itemAlias) -> {
115            document.add(new Field(IndexFields.itemNameAndAliases.name(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
116            return itemAlias;
117        }).forEach((itemAlias) -> {
118            document.add(new Field(itemAlias.getItemAliasType().getLastDetail().getItemAliasTypeName(), itemAlias.getAlias(), FieldTypes.NOT_STORED_TOKENIZED));
119        });
120
121        document.add(new Field(IndexFields.itemCategoryName.name(), itemDetail.getItemCategory().getLastDetail().getItemCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
122
123        if(itemAccountingCategory != null) {
124            document.add(new Field(IndexFields.itemAccountingCategoryName.name(), itemDetail.getItemAccountingCategory().getLastDetail().getItemAccountingCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
125        }
126
127        if(itemPurchasingCategory != null) {
128            document.add(new Field(IndexFields.itemPurchasingCategoryName.name(), itemDetail.getItemPurchasingCategory().getLastDetail().getItemPurchasingCategoryName(), FieldTypes.NOT_STORED_TOKENIZED));
129        }
130
131        if(inventorySerialized != null) {
132            document.add(new Field(IndexFields.inventorySerialized.name(), itemDetail.getInventorySerialized().toString(), FieldTypes.NOT_STORED_TOKENIZED));
133        }
134
135        document.add(new Field(IndexFields.shippingChargeExempt.name(), itemDetail.getShippingChargeExempt().toString(), FieldTypes.NOT_STORED_TOKENIZED));
136        document.add(new LongPoint(IndexFields.shippingStartTime.name(), itemDetail.getShippingStartTime()));
137        if(shippingEndTime != null) {
138            document.add(new LongPoint(IndexFields.shippingEndTime.name(), shippingEndTime));
139        }
140        document.add(new LongPoint(IndexFields.salesOrderStartTime.name(), itemDetail.getSalesOrderStartTime()));
141        if(salesOrderEndTime != null) {
142            document.add(new LongPoint(IndexFields.salesOrderEndTime.name(), salesOrderEndTime));
143        }
144        if(purchaseOrderStartTime != null) {
145            document.add(new LongPoint(IndexFields.purchaseOrderStartTime.name(), purchaseOrderStartTime));
146        }
147        if(purchaseOrderEndTime != null) {
148            document.add(new LongPoint(IndexFields.purchaseOrderEndTime.name(), purchaseOrderEndTime));
149        }
150        document.add(new Field(IndexFields.allowClubDiscounts.name(), itemDetail.getAllowClubDiscounts().toString(), FieldTypes.NOT_STORED_TOKENIZED));
151        document.add(new Field(IndexFields.allowCouponDiscounts.name(), itemDetail.getAllowCouponDiscounts().toString(), FieldTypes.NOT_STORED_TOKENIZED));
152        document.add(new Field(IndexFields.allowAssociatePayments.name(), itemDetail.getAllowAssociatePayments().toString(), FieldTypes.NOT_STORED_TOKENIZED));
153
154        document.add(new Field(IndexFields.unitOfMeasureKindName.name(), itemDetail.getUnitOfMeasureKind().getLastDetail().getUnitOfMeasureKindName(), FieldTypes.NOT_STORED_TOKENIZED));
155        document.add(new Field(IndexFields.itemPriceTypeName.name(), itemDetail.getItemPriceType().getItemPriceTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
156
157        itemDescriptionTypes.forEach((itemDescriptionType) -> {
158            var itemDescription = itemControl.getBestItemDescription(itemDescriptionType, item, language);
159            if (itemDescription != null) {
160                var itemDescriptionTypeDetail = itemDescriptionType.getLastDetail();
161                var mimeTypeUsageType = itemDescriptionTypeDetail.getMimeTypeUsageType();
162                var itemDescriptionTypeName = itemDescriptionTypeDetail.getItemDescriptionTypeName();
163
164                if(mimeTypeUsageType == null) {
165                    var itemStringDescription = itemControl.getItemStringDescription(itemDescription);
166                    var stringDescription = itemStringDescription.getStringDescription();
167
168                    document.add(new Field(itemDescriptionTypeName, stringDescription, FieldTypes.NOT_STORED_TOKENIZED));
169                    document.add(new Field(itemDescriptionTypeName + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.dictionary.name(),
170                            stringDescription, FieldTypes.NOT_STORED_TOKENIZED));
171                    document.add(new SortedDocValuesField(itemDescriptionTypeName + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
172                            new BytesRef(sortableDescriptionProducer.getSortableDescription(stringDescription))));
173
174                    if(IndexerDebugFlags.LogItemIndexing) {
175                        log.info("--- " + itemDescriptionTypeName + ", stringDescription = " + stringDescription);
176                    }
177                } else {
178                    var mimeTypeUsageTypeName = mimeTypeUsageType.getMimeTypeUsageTypeName();
179
180                    if(mimeTypeUsageTypeName.equals(MimeTypeUsageTypes.TEXT.name())) {
181                        var itemClobDescription = itemControl.getItemClobDescription(itemDescription);
182                        var clobDescription = itemClobDescription.getClobDescription();
183
184                        // TODO: mime type conversion to text/plain happens here
185                        document.add(new Field(itemDescriptionTypeName, clobDescription, FieldTypes.NOT_STORED_TOKENIZED));
186
187                        if(IndexerDebugFlags.LogItemIndexing) {
188                            log.info("--- " + itemDescriptionTypeName + ", clobDescription = " + clobDescription);
189                        }
190                    } // Others are not supported at this time, DOCUMENT probably should be.
191                }
192            }
193        });
194    }
195
196    private void addContentCatalogToDocument(final ContentCatalog contentCatalog, final Document document) {
197        var contentCatalogDetail = contentCatalog.getLastDetail();
198        var contentCollectionDetail = contentCatalogDetail.getContentCollection().getLastDetail();
199
200        document.add(new Field(IndexFields.contentCollectionName.name(), contentCollectionDetail.getContentCollectionName(),
201                FieldTypes.NOT_STORED_TOKENIZED));
202        document.add(new SortedDocValuesField(IndexFields.contentCollectionName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
203                new BytesRef(contentCollectionDetail.getContentCollectionName())));
204        document.add(new Field(IndexFields.contentCatalogName.name(), contentCatalogDetail.getContentCatalogName(), FieldTypes.NOT_STORED_TOKENIZED));
205        document.add(new SortedDocValuesField(IndexFields.contentCatalogName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
206                new BytesRef(contentCatalogDetail.getContentCatalogName())));
207    }
208
209    private void addContentCatalogItemToDocument(final ContentCatalogItem contentCatalogItem, final Document document) {
210        var contentControl = Session.getModelController(ContentControl.class);
211        var unitOfMeasureTypeDetail = contentCatalogItem.getUnitOfMeasureType().getLastDetail();
212
213        document.add(new Field(IndexFields.inventoryConditionName.name(), contentCatalogItem.getInventoryCondition().getLastDetail().getInventoryConditionName(), FieldTypes.NOT_STORED_TOKENIZED));
214        document.add(new Field(IndexFields.unitOfMeasureKindName.name(), unitOfMeasureTypeDetail.getUnitOfMeasureKind().getLastDetail().getUnitOfMeasureKindName(), FieldTypes.NOT_STORED_TOKENIZED));
215        document.add(new Field(IndexFields.unitOfMeasureTypeName.name(), unitOfMeasureTypeDetail.getUnitOfMeasureTypeName(), FieldTypes.NOT_STORED_TOKENIZED));
216        document.add(new Field(IndexFields.currencyIsoName.name(), contentCatalogItem.getCurrency().getCurrencyIsoName(), FieldTypes.NOT_STORED_TOKENIZED));
217
218        var contentCategoryItems = contentControl.getContentCategoryItemsByContentCatalogItem(contentCatalogItem);
219        var contentCategoryNamesBuilder = new StringBuilder();
220        if(!contentCategoryItems.isEmpty()) {
221            contentCategoryItems.forEach((contentCategoryItem) -> {
222                if(!contentCategoryNamesBuilder.isEmpty()) {
223                    contentCategoryNamesBuilder.append(' ');
224                }
225
226                contentCategoryNamesBuilder.append(contentCategoryItem.getContentCategory().getLastDetail().getContentCategoryName());
227            });
228            document.add(new Field(IndexFields.contentCategoryNames.name(), contentCategoryNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
229        }
230
231        var itemPriceType = ItemPriceTypes.valueOf(contentCatalogItem.getItem().getLastDetail().getItemPriceType().getItemPriceTypeName());
232        switch(itemPriceType) {
233            case FIXED -> {
234                var fixedItemPrice = contentControl.getContentCatalogItemFixedPrice(contentCatalogItem);
235
236                document.add(new LongPoint(IndexFields.unitPrice.name(), fixedItemPrice.getUnitPrice()));
237            }
238            case VARIABLE -> {
239                var variableItemPrice = contentControl.getContentCatalogItemVariablePrice(contentCatalogItem);
240
241                document.add(new LongPoint(IndexFields.minimumUnitPrice.name(), variableItemPrice.getMinimumUnitPrice()));
242                document.add(new LongPoint(IndexFields.maximumUnitPrice.name(), variableItemPrice.getMaximumUnitPrice()));
243                document.add(new LongPoint(IndexFields.unitPriceIncrement.name(), variableItemPrice.getUnitPriceIncrement()));
244            }
245        }
246    }
247
248    @Override
249    protected Document convertToDocument(final EntityInstance entityInstance, final ContentCatalogItem contentCatalogItem) {
250        var entityInstanceControl = Session.getModelController(EntityInstanceControl.class);
251        var document = newDocumentWithEntityInstanceFields(entityInstance, contentCatalogItem.getPrimaryKey());
252
253        addEntityInstanceFieldsToDocument(document, entityInstanceControl.getEntityInstanceByBasePK(contentCatalogItem.getItemPK()));
254        addEntityInstanceFieldsToDocument(document, entityInstanceControl.getEntityInstanceByBasePK(contentCatalogItem.getContentCatalogPK()));
255
256        addItemToDocument(contentCatalogItem.getItem(), document);
257        addContentCatalogToDocument(contentCatalogItem.getContentCatalog(), document);
258        addContentCatalogItemToDocument(contentCatalogItem, document);
259
260        return document;
261    }
262
263}