001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 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.control.user.index.server.command; 018 019import com.echothree.control.user.index.common.form.UpdateIndexesForm; 020import com.echothree.control.user.index.common.result.IndexResultFactory; 021import com.echothree.model.control.contact.server.indexer.ContactMechanismIndexer; 022import com.echothree.model.control.content.server.indexer.ContentCategoryIndexer; 023import com.echothree.model.control.core.server.indexer.EntityListItemIndexer; 024import com.echothree.model.control.core.server.indexer.EntityTypeIndexer; 025import com.echothree.model.control.customer.server.indexer.CustomerIndexer; 026import com.echothree.model.control.employee.server.indexer.EmployeeIndexer; 027import com.echothree.model.control.forum.server.indexer.ForumMessageIndexer; 028import com.echothree.model.control.index.common.IndexTypes; 029import com.echothree.model.control.index.server.control.IndexControl; 030import com.echothree.model.control.index.server.indexer.BaseIndexer; 031import com.echothree.model.control.item.server.indexer.HarmonizedTariffScheduleCodeIndexer; 032import com.echothree.model.control.item.server.indexer.ItemIndexer; 033import com.echothree.model.control.offer.server.indexer.OfferIndexer; 034import com.echothree.model.control.offer.server.indexer.UseIndexer; 035import com.echothree.model.control.offer.server.indexer.UseTypeIndexer; 036import com.echothree.model.control.party.common.PartyTypes; 037import com.echothree.model.control.queue.common.QueueTypes; 038import com.echothree.model.control.queue.server.control.QueueControl; 039import com.echothree.model.control.queue.server.logic.QueueTypeLogic; 040import com.echothree.model.control.search.server.logic.SearchLogic; 041import com.echothree.model.control.security.server.indexer.SecurityRoleGroupIndexer; 042import com.echothree.model.control.security.server.indexer.SecurityRoleIndexer; 043import com.echothree.model.control.vendor.server.indexer.VendorIndexer; 044import com.echothree.model.control.warehouse.server.indexer.WarehouseIndexer; 045import com.echothree.model.data.core.server.entity.EntityInstance; 046import com.echothree.model.data.core.server.entity.EntityType; 047import com.echothree.model.data.queue.common.QueuedEntityConstants; 048import com.echothree.model.data.queue.server.entity.QueueType; 049import com.echothree.model.data.queue.server.entity.QueuedEntity; 050import com.echothree.model.data.user.common.pk.UserVisitPK; 051import com.echothree.util.common.command.BaseResult; 052import com.echothree.util.common.transfer.Limit; 053import com.echothree.util.server.control.BaseSimpleCommand; 054import com.echothree.util.server.control.CommandSecurityDefinition; 055import com.echothree.util.server.control.PartyTypeDefinition; 056import com.echothree.util.server.persistence.Session; 057import com.echothree.util.server.persistence.ThreadSession; 058import static java.lang.Math.toIntExact; 059import java.util.ArrayList; 060import java.util.HashMap; 061import java.util.List; 062import java.util.Map; 063import java.util.Objects; 064 065public class UpdateIndexesCommand 066 extends BaseSimpleCommand<UpdateIndexesForm> { 067 068 private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION; 069 070 static { 071 COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(List.of( 072 new PartyTypeDefinition(PartyTypes.UTILITY.name(), null)) 073 ); 074 } 075 076 /** Creates a new instance of UpdateIndexesCommand */ 077 public UpdateIndexesCommand(UserVisitPK userVisitPK, UpdateIndexesForm form) { 078 super(userVisitPK, form, COMMAND_SECURITY_DEFINITION, null, false); 079 } 080 081 private static final int QUEUED_ENTITY_COUNT = 10; 082 private static final long MAXIMUM_MILLISECONDS = 90 * 1000; // 90 seconds 083 084 private void setLimits() { 085 var limits = new HashMap<String, Limit>(1); 086 087 limits.put(QueuedEntityConstants.ENTITY_TYPE_NAME, new Limit(Integer.toString(QUEUED_ENTITY_COUNT), null)); 088 session.setLimits(limits); 089 } 090 091 private Map<EntityInstance, List<QueuedEntity>> getQueuedEntities(final QueueType queueType) { 092 var queueControl = Session.getModelController(QueueControl.class); 093 var queuedEntityMap = new HashMap<EntityInstance, List<QueuedEntity>>(QUEUED_ENTITY_COUNT); 094 var queuedEntities = queueControl.getQueuedEntitiesByQueueType(queueType); 095 096 queuedEntities.stream().map(QueuedEntity::getEntityInstance).filter( 097 (entityInstance) -> !queuedEntityMap.containsKey(entityInstance)).forEach((entityInstance) -> { 098 List<QueuedEntity> duplicateQueuedEntities = queueControl.getQueuedEntities(queueType, entityInstance); 099 100 queuedEntityMap.put(entityInstance, duplicateQueuedEntities); 101 }); 102 103 return queuedEntityMap; 104 } 105 106 private void setupIndexers(final IndexControl indexControl, final Map<EntityType, List<BaseIndexer<?>>> indexersMap, final EntityType entityType) { 107 var indexTypes = indexControl.getIndexTypesByEntityType(entityType); 108 var size = 0L; 109 110 size = indexTypes.stream().map(indexControl::countIndexesByIndexType).reduce(size, Long::sum); 111 112 var indexers = new ArrayList<BaseIndexer<?>>(toIntExact(size)); 113 114 indexTypes.forEach((indexType) -> { 115 var indexes = indexControl.getIndexesByIndexType(indexType); 116 var indexTypeName = indexType.getLastDetail().getIndexTypeName(); 117 118 indexes.stream().map((index) -> { 119 BaseIndexer<?> baseIndexer = null; 120 121 if(indexTypeName.equals(IndexTypes.CUSTOMER.name())) { 122 baseIndexer = new CustomerIndexer(this, index); 123 } else if(indexTypeName.equals(IndexTypes.EMPLOYEE.name())) { 124 baseIndexer = new EmployeeIndexer(this, index); 125 } else if(indexTypeName.equals(IndexTypes.VENDOR.name())) { 126 baseIndexer = new VendorIndexer(this, index); 127 } else if(indexTypeName.equals(IndexTypes.ITEM.name())) { 128 baseIndexer = new ItemIndexer(this, index); 129 } else if(indexTypeName.equals(IndexTypes.FORUM_MESSAGE.name())) { 130 baseIndexer = new ForumMessageIndexer(this, index); 131 } else if(indexTypeName.equals(IndexTypes.ENTITY_LIST_ITEM.name())) { 132 baseIndexer = new EntityListItemIndexer(this, index); 133 } else if(indexTypeName.equals(IndexTypes.CONTENT_CATEGORY.name())) { 134 baseIndexer = new ContentCategoryIndexer(this, index); 135 } else if(indexTypeName.equals(IndexTypes.SECURITY_ROLE_GROUP.name())) { 136 baseIndexer = new SecurityRoleGroupIndexer(this, index); 137 } else if(indexTypeName.equals(IndexTypes.SECURITY_ROLE.name())) { 138 baseIndexer = new SecurityRoleIndexer(this, index); 139 } else if(indexTypeName.equals(IndexTypes.HARMONIZED_TARIFF_SCHEDULE_CODE.name())) { 140 baseIndexer = new HarmonizedTariffScheduleCodeIndexer(this, index); 141 } else if(indexTypeName.equals(IndexTypes.ENTITY_TYPE.name())) { 142 baseIndexer = new EntityTypeIndexer(this, index); 143 } else if(indexTypeName.equals(IndexTypes.CONTACT_MECHANISM.name())) { 144 baseIndexer = new ContactMechanismIndexer(this, index); 145 } else if(indexTypeName.equals(IndexTypes.OFFER.name())) { 146 baseIndexer = new OfferIndexer(this, index); 147 } else if(indexTypeName.equals(IndexTypes.USE.name())) { 148 baseIndexer = new UseIndexer(this, index); 149 } else if(indexTypeName.equals(IndexTypes.USE_TYPE.name())) { 150 baseIndexer = new UseTypeIndexer(this, index); 151 } else if(indexTypeName.equals(IndexTypes.WAREHOUSE.name())) { 152 baseIndexer = new WarehouseIndexer(this, index); 153 } 154 155 return baseIndexer; 156 }).filter(Objects::nonNull).peek(BaseIndexer::open).forEach(indexers::add); 157 }); 158 159 indexersMap.put(entityType, indexers); 160 } 161 162 private void indexQueuedEntity(final QueueControl queueControl, final Map<EntityType, List<BaseIndexer<?>>> indexersMap, 163 final Map.Entry<EntityInstance, List<QueuedEntity>> queuedEntityEntry) { 164 var entityInstance = queuedEntityEntry.getKey(); 165 var entityType = entityInstance.getEntityType(); 166 var baseIndexers = indexersMap.get(entityType); 167 168 for(var baseIndexer : baseIndexers) { 169 baseIndexer.updateIndex(entityInstance); 170 171 if(hasExecutionErrors()) { 172 break; 173 } 174 } 175 176 if(!hasExecutionErrors()) { 177 queuedEntityEntry.getValue().forEach(queueControl::removeQueuedEntity); 178 } 179 } 180 181 private void closeIndexers(final QueueControl queueControl, final QueueType queueType, final Map<EntityType, List<BaseIndexer<?>>> indexersMap) { 182 indexersMap.forEach((key, value) -> value.stream().peek((baseIndexer) -> { 183 if(queueControl.countQueuedEntitiesByEntityType(queueType, baseIndexer.getEntityType()) == 0) { 184 SearchLogic.getInstance().invalidateCachedSearchesByIndex(baseIndexer.getIndex()); 185 } 186 }).forEach(BaseIndexer::close)); 187 } 188 189 private void verifyIndexersAreSetup(final IndexControl indexControl, final Map<EntityType, List<BaseIndexer<?>>> indexersMap, 190 final Map<EntityInstance, List<QueuedEntity>> queuedEntityMap) { 191 for(Map.Entry<EntityInstance, List<QueuedEntity>> queuedEntityEntry : queuedEntityMap.entrySet()) { 192 var entityType = queuedEntityEntry.getKey().getEntityType(); 193 194 if(!indexersMap.containsKey(entityType)) { 195 setupIndexers(indexControl, indexersMap, entityType); 196 } 197 198 if(hasExecutionErrors()) { 199 break; 200 } 201 } 202 } 203 204 private void indexQueuedEntities(final QueueControl queueControl, final Map<EntityType, List<BaseIndexer<?>>> indexersMap, 205 final Map<EntityInstance, List<QueuedEntity>> queuedEntityMap) { 206 try { 207 ThreadSession.pushSessionEntityCache(); 208 209 for(Map.Entry<EntityInstance, List<QueuedEntity>> queuedEntityEntry : queuedEntityMap.entrySet()) { 210 indexQueuedEntity(queueControl, indexersMap, queuedEntityEntry); 211 212 if(hasExecutionErrors()) { 213 break; 214 } 215 } 216 } finally { 217 ThreadSession.popSessionEntityCache(); 218 } 219 } 220 221 @Override 222 protected BaseResult execute() { 223 var result = IndexResultFactory.getUpdateIndexesResult(); 224 var queueType = QueueTypeLogic.getInstance().getQueueTypeByName(this, QueueTypes.INDEXING.name()); 225 var indexingComplete = false; // Indexing is only complete when we can absolutely verify it as being complete. 226 227 if(!hasExecutionErrors()) { 228 var queueControl = Session.getModelController(QueueControl.class); 229 230 indexingComplete = queueControl.countQueuedEntitiesByQueueType(queueType) == 0; 231 232 // If there isn't anything in the queue, skip over all of this. 233 if(!indexingComplete) { 234 var indexControl = Session.getModelController(IndexControl.class); 235 var indexersMap = new HashMap<EntityType, List<BaseIndexer<?>>>(toIntExact(indexControl.countIndexes())); 236 237 try { 238 var exitTime = session.START_TIME + MAXIMUM_MILLISECONDS; 239 240 setLimits(); 241 242 while(System.currentTimeMillis() < exitTime) { 243 var queuedEntityMap = getQueuedEntities(queueType); 244 245 // If there are no more to index, break out of here. 246 if(queuedEntityMap.isEmpty()) { 247 break; 248 } 249 250 // Make sure we have the indexers available for each EntityType we've found. 251 verifyIndexersAreSetup(indexControl, indexersMap, queuedEntityMap); 252 253 if(!hasExecutionErrors()) { 254 indexQueuedEntities(queueControl, indexersMap, queuedEntityMap); 255 } 256 257 if(hasExecutionErrors()) { 258 break; 259 } 260 } 261 } finally { 262 closeIndexers(queueControl, queueType, indexersMap); 263 } 264 265 // Either the QueuedEntities have run out, or the time expired. Check to see which it is, and 266 // set indexingComplete to indicate if the QueuedEntities have run out. 267 indexingComplete = queueControl.countQueuedEntitiesByQueueType(queueType) == 0; 268 } 269 } 270 271 result.setIndexingComplete(indexingComplete); 272 273 return result; 274 } 275 276}