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}