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.search.server.command;
018
019import com.echothree.control.user.search.common.form.IdentifyForm;
020import com.echothree.control.user.search.common.result.SearchResultFactory;
021import com.echothree.model.control.core.common.CoreOptions;
022import com.echothree.model.control.core.common.CoreProperties;
023import com.echothree.model.control.core.common.transfer.ComponentVendorTransfer;
024import com.echothree.model.control.core.common.transfer.EntityInstanceTransfer;
025import com.echothree.model.control.core.common.transfer.EntityTypeTransfer;
026import com.echothree.model.control.core.server.control.CoreControl;
027import com.echothree.model.control.customer.server.search.CustomerSearchEvaluator;
028import com.echothree.model.control.item.server.control.ItemControl;
029import com.echothree.model.control.item.server.search.ItemSearchEvaluator;
030import com.echothree.model.control.party.server.control.PartyControl;
031import com.echothree.model.control.search.common.SearchKinds;
032import com.echothree.model.control.search.common.SearchTypes;
033import com.echothree.model.control.search.server.control.SearchControl;
034import com.echothree.model.control.search.server.logic.SearchLogic;
035import com.echothree.model.control.security.common.SecurityRoleGroups;
036import com.echothree.model.control.security.common.SecurityRoles;
037import com.echothree.model.control.security.server.logic.SecurityRoleLogic;
038import com.echothree.model.control.vendor.server.control.VendorControl;
039import com.echothree.model.control.vendor.server.search.VendorSearchEvaluator;
040import com.echothree.model.control.warehouse.server.control.WarehouseControl;
041import com.echothree.model.control.warehouse.server.search.WarehouseSearchEvaluator;
042import com.echothree.model.data.party.server.entity.Party;
043import com.echothree.model.data.search.server.entity.SearchKind;
044import com.echothree.model.data.search.server.entity.SearchType;
045import com.echothree.model.data.user.common.pk.UserVisitPK;
046import com.echothree.model.data.user.server.entity.UserVisit;
047import com.echothree.util.common.command.BaseResult;
048import com.echothree.util.common.string.NameResult;
049import com.echothree.util.common.validation.FieldDefinition;
050import com.echothree.util.common.validation.FieldType;
051import com.echothree.util.server.control.BaseSimpleCommand;
052import com.echothree.util.server.message.DummyExecutionErrorAccumulator;
053import com.echothree.util.server.persistence.EntityNamesUtils;
054import com.echothree.util.server.persistence.Session;
055import com.echothree.util.server.persistence.translator.EntityInstanceAndNames;
056import com.echothree.util.server.string.NameCleaner;
057import java.util.Arrays;
058import java.util.Collections;
059import java.util.HashSet;
060import java.util.List;
061import java.util.Set;
062
063public class IdentifyCommand
064        extends BaseSimpleCommand<IdentifyForm> {
065    
066    private final static List<FieldDefinition> FORM_FIELD_DEFINITIONS;
067    
068    static {
069        FORM_FIELD_DEFINITIONS = Collections.unmodifiableList(Arrays.asList(
070                new FieldDefinition("Target", FieldType.STRING, true, null, null)
071                ));
072    }
073    
074    /** Creates a new instance of IdentifyCommand */
075    public IdentifyCommand(UserVisitPK userVisitPK, IdentifyForm form) {
076        super(userVisitPK, form, null, FORM_FIELD_DEFINITIONS, true);
077    }
078
079    @Override
080    protected void setupSession() {
081        super.setupSession();
082
083        // Names are always included in the JumpResult, assembly of them is a little weird, so always disallow this option.
084        removeOption(CoreOptions.EntityInstanceIncludeNames);
085
086        // Ensure we're able to compare instances of and generate a HashCode for the EntityInstanceTransfers
087        removeFilteredTransferProperty(EntityInstanceTransfer.class, CoreProperties.ENTITY_TYPE);
088        removeFilteredTransferProperty(EntityTypeTransfer.class, CoreProperties.COMPONENT_VENDOR);
089        removeFilteredTransferProperty(EntityTypeTransfer.class, CoreProperties.ENTITY_TYPE_NAME);
090        removeFilteredTransferProperty(ComponentVendorTransfer.class, CoreProperties.COMPONENT_VENDOR_NAME);
091    }
092    
093    private EntityInstanceTransfer fillInEntityInstance(EntityInstanceAndNames entityInstanceAndNames) {
094        var entityInstance = getCoreControl().getEntityInstanceTransfer(getUserVisit(), entityInstanceAndNames.getEntityInstance(), false, false, false, false, false, false);
095
096        entityInstance.setEntityNames(entityInstanceAndNames.getEntityNames());
097        
098        return entityInstance;
099    }
100    
101    private void checkSequenceTypes(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
102        var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(party, target, true);
103        
104        if(entityInstanceAndNames != null) {
105            entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
106        }
107    }
108    
109    private void checkItems(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
110        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
111                SecurityRoleGroups.Item.name(), SecurityRoles.Search.name())) {
112            var itemControl = Session.getModelController(ItemControl.class);
113            var item = itemControl.getItemByNameThenAlias(target);
114
115            if(item != null) {
116                var entityInstance = getCoreControl().getEntityInstanceByBasePK(item.getPrimaryKey());
117                var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(entityInstance);
118
119                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
120            }
121        }
122    }
123
124    private void checkCompanies(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
125        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
126                SecurityRoleGroups.Company.name(), SecurityRoles.Search.name())) {
127            var partyControl = Session.getModelController(PartyControl.class);
128            var partyCompany = partyControl.getPartyCompanyByName(target);
129
130            if(partyCompany != null) {
131                var entityInstance = getCoreControl().getEntityInstanceByBasePK(partyCompany.getParty().getPrimaryKey());
132                var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(entityInstance);
133
134                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
135            }
136        }
137    }
138
139    private void checkDivisions(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
140        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
141                SecurityRoleGroups.Division.name(), SecurityRoles.Search.name())) {
142            var partyControl = Session.getModelController(PartyControl.class);
143            var partyDivisions = partyControl.getDivisionsByName(target);
144
145            partyDivisions.stream().map((partyDivision) -> getCoreControl().getEntityInstanceByBasePK(partyDivision.getParty().getPrimaryKey())).map((entityInstance) -> EntityNamesUtils.getInstance().getEntityNames(entityInstance)).forEach((entityInstanceAndNames) -> {
146                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
147            });
148        }
149    }
150
151    private void checkDepartments(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
152        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
153                SecurityRoleGroups.Department.name(), SecurityRoles.Search.name())) {
154            var partyControl = Session.getModelController(PartyControl.class);
155            var partyDepartments = partyControl.getDepartmentsByName(target);
156
157            partyDepartments.stream().map((partyDepartment) -> getCoreControl().getEntityInstanceByBasePK(partyDepartment.getParty().getPrimaryKey())).map((entityInstance) -> EntityNamesUtils.getInstance().getEntityNames(entityInstance)).forEach((entityInstanceAndNames) -> {
158                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
159            });
160        }
161    }
162
163    private void checkWarehouses(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
164        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
165                SecurityRoleGroups.Warehouse.name(), SecurityRoles.Search.name())) {
166            var warehouseControl = Session.getModelController(WarehouseControl.class);
167            var warehouse = warehouseControl.getWarehouseByName(target);
168
169            if(warehouse != null) {
170                var entityInstance = getCoreControl().getEntityInstanceByBasePK(warehouse.getParty().getPrimaryKey());
171                var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(entityInstance);
172
173                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
174            }
175        }
176    }
177
178    private void checkLocations(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
179        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
180                SecurityRoleGroups.Location.name(), SecurityRoles.Search.name())) {
181            var warehouseControl = Session.getModelController(WarehouseControl.class);
182            var locations = warehouseControl.getLocationsByName(target);
183
184            locations.stream().map((location) -> getCoreControl().getEntityInstanceByBasePK(location.getPrimaryKey())).map((entityInstance) -> EntityNamesUtils.getInstance().getEntityNames(entityInstance)).forEach((entityInstanceAndNames) -> {
185                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
186            });
187        }
188    }
189
190    private void checkVendors(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
191        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
192                SecurityRoleGroups.Vendor.name(), SecurityRoles.Search.name())) {
193            var vendorControl = Session.getModelController(VendorControl.class);
194            var vendor = vendorControl.getVendorByName(target);
195
196            if(vendor != null) {
197                var entityInstance = getCoreControl().getEntityInstanceByBasePK(vendor.getParty().getPrimaryKey());
198                var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(entityInstance);
199
200                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
201            }
202        }
203    }
204
205    private void checkVendorItems(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
206        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
207                SecurityRoleGroups.VendorItem.name(), SecurityRoles.Search.name())) {
208            var vendorControl = Session.getModelController(VendorControl.class);
209            var vendorItems = vendorControl.getVendorItemsByVendorItemName(target);
210
211            vendorItems.stream().map((vendorItem) -> getCoreControl().getEntityInstanceByBasePK(vendorItem.getPrimaryKey())).map((entityInstance) -> EntityNamesUtils.getInstance().getEntityNames(entityInstance)).forEach((entityInstanceAndNames) -> {
212                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
213            });
214        }
215    }
216
217    private void checkComponentVendors(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
218        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
219                SecurityRoleGroups.ComponentVendor.name(), SecurityRoles.Search.name())) {
220            var componentVendor = getCoreControl().getComponentVendorByName(target);
221
222            if(componentVendor != null) {
223                var entityInstance = getCoreControl().getEntityInstanceByBasePK(componentVendor.getPrimaryKey());
224                var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(entityInstance);
225
226                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
227            }
228        }
229    }
230
231    private void checkEntityTypes(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
232        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
233                SecurityRoleGroups.EntityType.name(), SecurityRoles.Search.name())) {
234            var coreControl = Session.getModelController(CoreControl.class);
235            var entityTypes = coreControl.getEntityTypesByName(target);
236
237            entityTypes.stream().map((entityType) -> getCoreControl().getEntityInstanceByBasePK(entityType.getPrimaryKey())).map((entityInstance) -> EntityNamesUtils.getInstance().getEntityNames(entityInstance)).forEach((entityInstanceAndNames) -> {
238                entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
239            });
240        }
241    }
242
243    private void executeCustomerSearch(final UserVisit userVisit, final Set<EntityInstanceTransfer> entityInstances,
244            final SearchLogic searchLogic, final SearchKind searchKind, final SearchType searchType,
245            final String firstName, final String middleName, final String lastName, final String q) {
246        var customerSearchEvaluator = new CustomerSearchEvaluator(userVisit, searchType,
247                searchLogic.getDefaultSearchDefaultOperator(null),
248                searchLogic.getDefaultSearchSortOrder(null, searchKind),
249                searchLogic.getDefaultSearchSortDirection(null));
250
251        customerSearchEvaluator.setFirstName(firstName);
252        customerSearchEvaluator.setFirstNameSoundex(false);
253        customerSearchEvaluator.setMiddleName(middleName);
254        customerSearchEvaluator.setMiddleNameSoundex(false);
255        customerSearchEvaluator.setLastName(lastName);
256        customerSearchEvaluator.setLastNameSoundex(false);
257        customerSearchEvaluator.setQ(null, q);
258
259        // Avoid using the real ExecutionErrorAccumulator in order to avoid either throwing an Exception or
260        // accumulating errors for this UC.
261        var dummyExecutionErrorAccumulator = new DummyExecutionErrorAccumulator();
262        customerSearchEvaluator.execute(dummyExecutionErrorAccumulator);
263
264        if(!dummyExecutionErrorAccumulator.hasExecutionErrors()) {
265            addSearchResults(userVisit, searchType, entityInstances);
266        }
267    }
268
269    private void searchCustomers(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target,
270            final NameResult nameResult) {
271        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
272                SecurityRoleGroups.Customer.name(), SecurityRoles.Search.name())) {
273            var userVisit = getUserVisit();
274            var searchLogic = SearchLogic.getInstance();
275            var searchKind = searchLogic.getSearchKindByName(null, SearchKinds.CUSTOMER.name());
276            var searchType = searchLogic.getSearchTypeByName(null, searchKind, SearchTypes.IDENTIFY.name());
277
278            // First attempt using a first and/or last name isolated from target.
279            executeCustomerSearch(userVisit, entityInstances, searchLogic, searchKind, searchType,
280                    nameResult.getFirstName(), nameResult.getMiddleName(), nameResult.getLastName(), null);
281            // Then attempt searching for target using it as a query string.
282            executeCustomerSearch(userVisit, entityInstances, searchLogic, searchKind, searchType,
283                    null, null, null, target);
284        }
285    }
286
287    private void executeVendorSearch(final UserVisit userVisit, final Set<EntityInstanceTransfer> entityInstances,
288            final SearchLogic searchLogic, final SearchKind searchKind, final SearchType searchType,
289            final String firstName, final String middleName, final String lastName, final String q) {
290        var vendorSearchEvaluator = new VendorSearchEvaluator(userVisit, searchType,
291                searchLogic.getDefaultSearchDefaultOperator(null),
292                searchLogic.getDefaultSearchSortOrder(null, searchKind),
293                searchLogic.getDefaultSearchSortDirection(null));
294
295        vendorSearchEvaluator.setFirstName(firstName);
296        vendorSearchEvaluator.setFirstNameSoundex(false);
297        vendorSearchEvaluator.setMiddleName(middleName);
298        vendorSearchEvaluator.setMiddleNameSoundex(false);
299        vendorSearchEvaluator.setLastName(lastName);
300        vendorSearchEvaluator.setLastNameSoundex(false);
301        vendorSearchEvaluator.setQ(null, q);
302
303        // Avoid using the real ExecutionErrorAccumulator in order to avoid either throwing an Exception or
304        // accumulating errors for this UC.
305        var dummyExecutionErrorAccumulator = new DummyExecutionErrorAccumulator();
306        vendorSearchEvaluator.execute(dummyExecutionErrorAccumulator);
307
308        if(!dummyExecutionErrorAccumulator.hasExecutionErrors()) {
309            addSearchResults(userVisit, searchType, entityInstances);
310        }
311    }
312
313    private void searchVendors(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target,
314            final NameResult nameResult) {
315        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
316                SecurityRoleGroups.Vendor.name(), SecurityRoles.Search.name())) {
317            var userVisit = getUserVisit();
318            var searchLogic = SearchLogic.getInstance();
319            var searchKind = searchLogic.getSearchKindByName(null, SearchKinds.VENDOR.name());
320            var searchType = searchLogic.getSearchTypeByName(null, searchKind, SearchTypes.IDENTIFY.name());
321
322            // First attempt using a first and/or last name isolated from target.
323            executeVendorSearch(userVisit, entityInstances, searchLogic, searchKind, searchType,
324                    nameResult.getFirstName(), nameResult.getMiddleName(), nameResult.getLastName(), null);
325            // Then attempt searching for target using it as a query string.
326            executeVendorSearch(userVisit, entityInstances, searchLogic, searchKind, searchType,
327                    null, null, null, target);
328        }
329    }
330
331    private void executeWarehouseSearch(final UserVisit userVisit, final Set<EntityInstanceTransfer> entityInstances,
332            final SearchLogic searchLogic, final SearchKind searchKind, final SearchType searchType, final String q) {
333        var warehouseSearchEvaluator = new WarehouseSearchEvaluator(userVisit, searchType,
334                searchLogic.getDefaultSearchDefaultOperator(null),
335                searchLogic.getDefaultSearchSortOrder(null, searchKind),
336                searchLogic.getDefaultSearchSortDirection(null));
337
338        warehouseSearchEvaluator.setQ(null, q);
339
340        // Avoid using the real ExecutionErrorAccumulator in order to avoid either throwing an Exception or
341        // accumulating errors for this UC.
342        var dummyExecutionErrorAccumulator = new DummyExecutionErrorAccumulator();
343        warehouseSearchEvaluator.execute(dummyExecutionErrorAccumulator);
344
345        if(!dummyExecutionErrorAccumulator.hasExecutionErrors()) {
346            addSearchResults(userVisit, searchType, entityInstances);
347        }
348    }
349
350    private void searchWarehouses(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
351        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
352                SecurityRoleGroups.Warehouse.name(), SecurityRoles.Search.name())) {
353            var userVisit = getUserVisit();
354            var searchLogic = SearchLogic.getInstance();
355            var searchKind = searchLogic.getSearchKindByName(null, SearchKinds.WAREHOUSE.name());
356            var searchType = searchLogic.getSearchTypeByName(null, searchKind, SearchTypes.IDENTIFY.name());
357
358            // Attempt searching for target using it as a query string.
359            executeWarehouseSearch(userVisit, entityInstances, searchLogic, searchKind, searchType,
360                    target);
361        }
362    }
363
364    private void executeItemSearch(final UserVisit userVisit, final Set<EntityInstanceTransfer> entityInstances,
365            final SearchLogic searchLogic, final SearchKind searchKind, final SearchType searchType,
366            final String q) {
367        var itemSearchEvaluator = new ItemSearchEvaluator(userVisit, null, searchType,
368                searchLogic.getDefaultSearchDefaultOperator(null),
369                searchLogic.getDefaultSearchSortOrder(null, searchKind),
370                searchLogic.getDefaultSearchSortDirection(null),
371                null);
372
373        itemSearchEvaluator.setQ(null, q);
374
375        // Avoid using the real ExecutionErrorAccumulator in order to avoid either throwing an Exception or
376        // accumulating errors for this UC.
377        var dummyExecutionErrorAccumulator = new DummyExecutionErrorAccumulator();
378        itemSearchEvaluator.execute(dummyExecutionErrorAccumulator);
379
380        if(!dummyExecutionErrorAccumulator.hasExecutionErrors()) {
381            addSearchResults(userVisit, searchType, entityInstances);
382        }
383    }
384
385    private void searchItems(final Party party, final Set<EntityInstanceTransfer> entityInstances, final String target) {
386        if(SecurityRoleLogic.getInstance().hasSecurityRoleUsingNames(null, party,
387                SecurityRoleGroups.Item.name(), SecurityRoles.Search.name())) {
388            var userVisit = getUserVisit();
389            var searchLogic = SearchLogic.getInstance();
390            var searchKind = searchLogic.getSearchKindByName(null, SearchKinds.ITEM.name());
391            var searchType = searchLogic.getSearchTypeByName(null, searchKind, SearchTypes.IDENTIFY.name());
392
393            executeItemSearch(userVisit, entityInstances, searchLogic, searchKind, searchType, target);
394        }
395    }
396
397    // Add results from any of the BaseSearchEvaluators to the entityInstances.
398    private void addSearchResults(final UserVisit userVisit, final SearchType searchType,
399            final Set<EntityInstanceTransfer> entityInstances) {
400        var searchControl = Session.getModelController(SearchControl.class);
401        var userVisitSearch = SearchLogic.getInstance().getUserVisitSearch(null, userVisit, searchType);
402        var resultEntityInstances = searchControl.getUserVisitSearchEntityInstances(userVisitSearch);
403
404        for(var resultEntityInstance : resultEntityInstances) {
405            var entityInstanceAndNames = EntityNamesUtils.getInstance().getEntityNames(resultEntityInstance);
406
407            entityInstances.add(fillInEntityInstance(entityInstanceAndNames));
408        }
409    }
410
411    @Override
412    protected BaseResult execute() {
413        var result = SearchResultFactory.getIdentifyResult();
414        var entityInstances = new HashSet<EntityInstanceTransfer>();
415        var target = form.getTarget();
416        var party = getParty();
417        
418        // Compile a list of all possible EntityInstances that the target may refer to.
419        checkSequenceTypes(party, entityInstances, target);
420        checkItems(party, entityInstances, target);
421        checkCompanies(party, entityInstances, target);
422        checkDivisions(party, entityInstances, target);
423        checkDepartments(party, entityInstances, target);
424        checkWarehouses(party, entityInstances, target);
425        checkLocations(party, entityInstances, target);
426        checkVendors(party, entityInstances, target);
427        checkVendorItems(party, entityInstances, target);
428        checkComponentVendors(party, entityInstances, target);
429        checkEntityTypes(party, entityInstances, target);
430
431        var nameResult = new NameCleaner().getCleansedName(target);
432        searchCustomers(party, entityInstances, target, nameResult);
433        searchVendors(party, entityInstances, target, nameResult);
434        searchWarehouses(party, entityInstances, target);
435        searchItems(party, entityInstances, target);
436
437        result.setEntityInstances(entityInstances);
438        
439        return result;
440    }
441    
442}