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}