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.util.server.control;
018
019import com.echothree.control.user.party.common.spec.PartySpec;
020import com.echothree.model.control.core.common.CommandMessageTypes;
021import com.echothree.model.control.core.common.ComponentVendors;
022import com.echothree.model.control.core.common.EventTypes;
023import com.echothree.model.control.core.server.control.CommandControl;
024import com.echothree.model.control.core.server.control.ComponentControl;
025import com.echothree.model.control.core.server.control.CoreControl;
026import com.echothree.model.control.core.server.control.EntityTypeControl;
027import com.echothree.model.control.core.server.control.EventControl;
028import com.echothree.model.control.party.common.PartyTypes;
029import com.echothree.model.control.security.server.logic.SecurityRoleLogic;
030import com.echothree.model.control.user.server.control.UserControl;
031import com.echothree.model.control.user.server.logic.UserSessionLogic;
032import com.echothree.model.data.accounting.server.entity.Currency;
033import com.echothree.model.data.core.server.entity.EntityInstance;
034import com.echothree.model.data.core.server.entity.Event;
035import com.echothree.model.data.party.common.pk.PartyPK;
036import com.echothree.model.data.party.server.entity.DateTimeFormat;
037import com.echothree.model.data.party.server.entity.Language;
038import com.echothree.model.data.party.server.entity.Party;
039import com.echothree.model.data.party.server.entity.PartyType;
040import com.echothree.model.data.party.server.entity.TimeZone;
041import com.echothree.model.data.user.common.pk.UserVisitPK;
042import com.echothree.model.data.user.server.entity.UserSession;
043import com.echothree.model.data.user.server.entity.UserVisit;
044import com.echothree.model.data.user.server.factory.UserVisitFactory;
045import com.echothree.util.common.command.BaseResult;
046import com.echothree.util.common.command.CommandResult;
047import com.echothree.util.common.command.ExecutionResult;
048import com.echothree.util.common.command.SecurityResult;
049import com.echothree.util.common.exception.BaseException;
050import com.echothree.util.common.form.ValidationResult;
051import com.echothree.util.common.message.Message;
052import com.echothree.util.common.message.Messages;
053import com.echothree.util.common.message.SecurityMessages;
054import com.echothree.util.common.persistence.BasePK;
055import com.echothree.util.common.transfer.BaseTransfer;
056import com.echothree.util.server.cdi.CommandScopeExtension;
057import com.echothree.util.server.message.ExecutionErrorAccumulator;
058import com.echothree.util.server.message.ExecutionWarningAccumulator;
059import com.echothree.util.server.message.MessageUtils;
060import com.echothree.util.server.message.SecurityMessageAccumulator;
061import com.echothree.util.server.persistence.EntityPermission;
062import com.echothree.util.server.persistence.Session;
063import com.echothree.util.server.persistence.ThreadSession;
064import java.nio.charset.StandardCharsets;
065import java.util.concurrent.Future;
066import javax.ejb.AsyncResult;
067import javax.inject.Inject;
068import org.apache.commons.logging.Log;
069import org.apache.commons.logging.LogFactory;
070
071public abstract class BaseCommand
072        implements ExecutionWarningAccumulator, ExecutionErrorAccumulator, SecurityMessageAccumulator {
073
074    private Log log;
075
076    private final CommandSecurityDefinition commandSecurityDefinition;
077
078    protected Session session;
079
080    private UserVisitPK userVisitPK;
081    private UserVisit userVisit;
082    private UserSession userSession;
083    
084    private Party party;
085    
086    private Messages executionWarnings;
087    private Messages executionErrors;
088    private Messages securityMessages;
089    
090    private String componentVendorName;
091    private String commandName;
092    
093    private boolean checkIdentityVerifiedTime = true;
094    private boolean updateLastCommandTime = true;
095    private boolean logCommand = true;
096
097    @Inject
098    protected UserControl userControl;
099
100    @Inject
101    protected CoreControl coreControl;
102
103    @Inject
104    protected ComponentControl componentControl;
105
106    @Inject
107    protected EntityTypeControl entityTypeControl;
108
109    @Inject
110    protected EventControl eventControl;
111
112    @Inject
113    protected CommandControl commandControl;
114
115//    @Inject
116//    protected LicenseCheckLogic licenseCheckLogic;
117
118    @Inject
119    protected SecurityRoleLogic securityRoleLogic;
120
121    protected BaseCommand(CommandSecurityDefinition commandSecurityDefinition) {
122        if(ControlDebugFlags.LogBaseCommands) {
123            getLog().info("BaseCommand()");
124        }
125
126        this.commandSecurityDefinition = commandSecurityDefinition;
127    }
128
129    protected Log getLog() {
130        if(log == null) {
131            log = LogFactory.getLog(this.getClass());
132        }
133        
134        return log;
135    }
136    
137    private void setupNames() {
138        Class<? extends BaseCommand> c = this.getClass();
139        var className = c.getName();
140        var nameOffset = className.lastIndexOf('.');
141        
142        componentVendorName = ComponentVendors.ECHO_THREE.name();
143        commandName = new String(className.getBytes(StandardCharsets.UTF_8), nameOffset + 1, className.length() - nameOffset - 8, StandardCharsets.UTF_8);
144    }
145    
146    public String getComponentVendorName() {
147        if(componentVendorName == null) {
148            setupNames();
149        }
150        
151        return componentVendorName;
152    }
153    
154    public String getCommandName() {
155        if(commandName == null) {
156            setupNames();
157        }
158        
159        return commandName;
160    }
161    
162    public Party getCompanyParty() {
163        Party companyParty = null;
164        var partyRelationship = userSession.getPartyRelationship();
165        
166        if(partyRelationship != null) {
167            companyParty = partyRelationship.getFromParty();
168        }
169        
170        return companyParty;
171    }
172    
173    public PartyPK getPartyPK() {
174        if(party == null) {
175            getParty();
176        }
177        
178        return party == null? null: party.getPrimaryKey();
179    }
180    
181    public Party getParty() {
182        if(party == null) {
183            party = userControl.getPartyFromUserVisitPK(userVisitPK);
184        }
185        
186        return party;
187    }
188    
189    public PartyType getPartyType() {
190        PartyType partyType = null;
191        
192        if(getParty() != null) {
193            partyType = party.getLastDetail().getPartyType();
194        }
195        
196        return partyType;
197    }
198    
199    public String getPartyTypeName() {
200        var partyType = getPartyType();
201
202        return partyType == null ? null : partyType.getPartyTypeName();
203    }
204    
205    public UserVisitPK getUserVisitPK() {
206        return userVisitPK;
207    }
208
209    public void setUserVisitPK(UserVisitPK userVisitPK) {
210        this.userVisitPK = userVisitPK;
211        userVisit = null;
212    }
213
214    private UserVisit getUserVisit(EntityPermission entityPermission) {
215        if(userVisitPK != null) {
216            if(userVisit == null) {
217                userVisit = UserVisitFactory.getInstance().getEntityFromPK(entityPermission, userVisitPK);
218            } else {
219                if(entityPermission.equals(EntityPermission.READ_WRITE)) {
220                    if(!userVisit.getEntityPermission().equals(EntityPermission.READ_WRITE)) {
221                        userVisit = UserVisitFactory.getInstance().getEntityFromPK(EntityPermission.READ_WRITE, userVisitPK);
222                    }
223                }
224            }
225        }
226
227        return userVisit;
228    }
229    
230    public UserVisit getUserVisit() {
231        return getUserVisit(EntityPermission.READ_ONLY);
232    }
233    
234    public UserVisit getUserVisitForUpdate() {
235        return getUserVisit(EntityPermission.READ_WRITE);
236    }
237    
238    public UserSession getUserSession() {
239        return userSession;
240    }
241    
242    public Session getSession() {
243        return session;
244    }
245    
246    public UserControl getUserControl() {
247        return userControl;
248    }
249    
250    public Language getPreferredLanguage() {
251        return userControl.getPreferredLanguageFromUserVisit(getUserVisit());
252    }
253    
254    public Language getPreferredLanguage(Party party) {
255        return userControl.getPreferredLanguageFromParty(party);
256    }
257    
258    public Currency getPreferredCurrency() {
259        return userControl.getPreferredCurrencyFromUserVisit(getUserVisit());
260    }
261    
262    public Currency getPreferredCurrency(Party party) {
263        return userControl.getPreferredCurrencyFromParty(party);
264    }
265    
266    public TimeZone getPreferredTimeZone() {
267        return userControl.getPreferredTimeZoneFromUserVisit(getUserVisit());
268    }
269    
270    public TimeZone getPreferredTimeZone(Party party) {
271        return userControl.getPreferredTimeZoneFromParty(party);
272    }
273    
274    public DateTimeFormat getPreferredDateTimeFormat() {
275        return userControl.getPreferredDateTimeFormatFromUserVisit(getUserVisit());
276    }
277    
278    public DateTimeFormat getPreferredDateTimeFormat(Party party) {
279        return userControl.getPreferredDateTimeFormatFromParty(party);
280    }
281    
282    public boolean getCheckIdentityVerifiedTime() {
283        return checkIdentityVerifiedTime;
284    }
285
286    public void setCheckIdentityVerifiedTime(boolean checkIdentityVerifiedTime) {
287        this.checkIdentityVerifiedTime = checkIdentityVerifiedTime;
288    }
289
290    public boolean getUpdateLastCommandTime() {
291        return updateLastCommandTime;
292    }
293
294    public void setUpdateLastCommandTime(boolean updateLastCommandTime) {
295        this.updateLastCommandTime = updateLastCommandTime;
296    }
297
298    public boolean getLogCommand() {
299        return logCommand;
300    }
301
302    public void setLogCommand(boolean logCommand) {
303        this.logCommand = logCommand;
304    }
305
306    private void checkUserVisit() {
307        if(getUserVisit() != null) {
308            userSession = userControl.getUserSessionByUserVisit(userVisit);
309
310            if(userSession != null && checkIdentityVerifiedTime) {
311                var identityVerifiedTime = userSession.getIdentityVerifiedTime();
312
313                if(identityVerifiedTime != null) {
314                    var timeSinceLastCommand = session.getStartTime() - userVisit.getLastCommandTime();
315
316                    // If it has been > 15 minutes since their last command, invalidate the UserSession.
317                    if(timeSinceLastCommand > 15 * 60 * 1000) {
318                        userSession = UserSessionLogic.getInstance().invalidateUserSession(userSession);
319                    }
320                }
321            }
322        }
323    }
324
325    protected CommandSecurityDefinition getCommandSecurityDefinition() {
326        return commandSecurityDefinition;
327    }
328    
329    // Returns true if everything passes.
330    protected boolean checkCommandSecurityDefinition() {
331        var passed = true;
332        var myCommandSecurityDefinition = getCommandSecurityDefinition();
333        
334        if(myCommandSecurityDefinition != null) {
335            var partyTypeName = getParty() == null ? null : party.getLastDetail().getPartyType().getPartyTypeName();
336            var foundPartyType = false;
337            var foundPartySecurityRole = false;
338
339            for(var partyTypeDefinition : myCommandSecurityDefinition.getPartyTypeDefinitions()) {
340                if(partyTypeName == null) {
341                    if(partyTypeDefinition.getPartyTypeName() == null) {
342                        foundPartyType = true;
343                        foundPartySecurityRole = true;
344                        break;
345                    }
346                } else {
347                    if(partyTypeDefinition.getPartyTypeName().equals(partyTypeName)) {
348                        var securityRoleDefinitions = partyTypeDefinition.getSecurityRoleDefinitions();
349
350                        if(securityRoleDefinitions == null) {
351                            foundPartySecurityRole = true;
352                        } else {
353                            for(var securityRoleDefinition : securityRoleDefinitions) {
354                                var securityRoleGroupName = securityRoleDefinition.getSecurityRoleGroupName();
355                                var securityRoleName = securityRoleDefinition.getSecurityRoleName();
356
357                                if(securityRoleGroupName != null && securityRoleName != null) {
358                                    foundPartySecurityRole = securityRoleLogic.hasSecurityRoleUsingNames(this, party, securityRoleGroupName,
359                                            securityRoleName);
360                                }
361
362                                if(foundPartySecurityRole) {
363                                    break;
364                                }
365                            }
366                        }
367
368                        foundPartyType = true;
369                        break;
370                    }
371                }
372            }
373
374            if(!foundPartyType || !foundPartySecurityRole) {
375                passed = false;
376            }
377        }
378        
379        return passed;
380    }
381
382    // Returns true if everything passes.
383    protected boolean checkOptionalSecurityRoles() {
384        return true;
385    }
386
387    protected SecurityResult security() {
388        if(!(checkCommandSecurityDefinition() && checkOptionalSecurityRoles())) {
389            addSecurityMessage(SecurityMessages.InsufficientSecurity.name());
390        }
391
392        return securityMessages == null ? null : new SecurityResult(securityMessages);
393    }
394    
395    @Override
396    public void addSecurityMessage(Message message) {
397        if(securityMessages == null) {
398            securityMessages = new Messages();
399        }
400        
401        securityMessages.add(Messages.SECURITY_MESSAGE, message);
402    }
403    
404    @Override
405    public void addSecurityMessage(String key, Object... values) {
406        addSecurityMessage(new Message(key, values));
407    }
408    
409    @Override
410    public Messages getSecurityMessages() {
411        return securityMessages;
412    }
413    
414    @Override
415    public boolean hasSecurityMessages() {
416        return securityMessages != null && securityMessages.size(Messages.SECURITY_MESSAGE) != 0;
417    }
418    
419    protected ValidationResult validate() {
420        if(ControlDebugFlags.LogBaseCommands) {
421            log.info("validate()");
422        }
423        
424        return null;
425    }
426    
427    protected abstract BaseResult execute();
428    
429    @Override
430    public void addExecutionWarning(Message message) {
431        if(executionWarnings == null) {
432            executionWarnings = new Messages();
433        }
434        
435        executionWarnings.add(Messages.EXECUTION_WARNING, message);
436    }
437    
438    @Override
439    public void addExecutionWarning(String key, Object... values) {
440        addExecutionWarning(new Message(key, values));
441    }
442    
443    @Override
444    public Messages getExecutionWarnings() {
445        return executionWarnings;
446    }
447    
448    @Override
449    public boolean hasExecutionWarnings() {
450        return executionWarnings != null && executionWarnings.size(Messages.EXECUTION_WARNING) != 0;
451    }
452    
453    @Override
454    public void addExecutionError(Message message) {
455        if(executionErrors == null) {
456            executionErrors = new Messages();
457        }
458        
459        executionErrors.add(Messages.EXECUTION_ERROR, message);
460    }
461    
462    @Override
463    public void addExecutionError(String key, Object... values) {
464        addExecutionError(new Message(key, values));
465    }
466    
467    @Override
468    public Messages getExecutionErrors() {
469        return executionErrors;
470    }
471    
472    @Override
473    public boolean hasExecutionErrors() {
474        return executionErrors != null && executionErrors.size(Messages.EXECUTION_ERROR) != 0;
475    }
476    
477    protected BaseResult getBaseResultAfterErrors() {
478        return null;
479    }
480
481    protected void setupSession() {
482        initSession();
483    }
484
485    // Called by setupSession() and canQueryByGraphQl()
486    protected void initSession() {
487        session = ThreadSession.currentSession();
488    }
489
490    protected void teardownSession() {
491        session = null;
492    }
493
494    public <R extends BaseResult> Future<CommandResult<R>> runAsync(UserVisitPK userVisitPK) {
495        return new AsyncResult<>(run(userVisitPK));
496    }
497
498    @SuppressWarnings("unchecked")
499    public <R extends BaseResult> CommandResult<R> run(UserVisitPK userVisitPK)
500            throws BaseException {
501        if(ControlDebugFlags.LogBaseCommands) {
502            log.info(">>> run()");
503        }
504
505        this.userVisitPK = userVisitPK;
506
507        if(CommandScopeExtension.getCommandScopeContext().isActive()) {
508            CommandScopeExtension.getCommandScopeContext().push();
509        } else {
510            CommandScopeExtension.getCommandScopeContext().activate();
511        }
512        setupSession();
513
514        SecurityResult securityResult;
515        ValidationResult validationResult = null;
516        ExecutionResult<R> executionResult;
517        CommandResult<R> commandResult;
518
519        try {
520            BaseResult baseResult = null;
521
522//            if(licenseCheckLogic.permitExecution(session)) {
523                checkUserVisit();
524                securityResult = security();
525
526                if(securityResult == null || !securityResult.getHasMessages()) {
527                    validationResult = validate();
528
529                    if(validationResult == null || !validationResult.getHasErrors()) {
530                        baseResult = execute();
531                    }
532                }
533//            } else {
534//                addExecutionError(ExecutionErrors.LicenseCheckFailed.name());
535//            }
536
537            executionResult = new ExecutionResult<>(executionWarnings, executionErrors, (R)(baseResult == null ? getBaseResultAfterErrors() : baseResult));
538
539            // Don't waste time getting the preferredLanguage if we don't need to.
540            if((securityResult != null && securityResult.getHasMessages())
541                    || (executionResult.getHasWarnings() || executionResult.getHasErrors())
542                    || (validationResult != null && validationResult.getHasErrors())) {
543                var preferredLanguage = getPreferredLanguage();
544
545                if(securityResult != null) {
546                    MessageUtils.getInstance().fillInMessages(preferredLanguage, CommandMessageTypes.Security.name(), securityResult.getSecurityMessages());
547                }
548
549                MessageUtils.getInstance().fillInMessages(preferredLanguage, CommandMessageTypes.Warning.name(), executionResult.getExecutionWarnings());
550                MessageUtils.getInstance().fillInMessages(preferredLanguage, CommandMessageTypes.Error.name(), executionResult.getExecutionErrors());
551
552                if(validationResult != null) {
553                    MessageUtils.getInstance().fillInMessages(preferredLanguage, CommandMessageTypes.Validation.name(), validationResult.getValidationMessages());
554                }
555            }
556
557            if(updateLastCommandTime) {
558                if(getUserVisitForUpdate() == null) {
559                    getLog().error("Command not logged, unknown userVisit");
560                } else {
561                    userVisit.setLastCommandTime(Math.max(session.getStartTime(), userVisit.getLastCommandTime()));
562
563                    // TODO: Check PartyTypeAuditPolicy to see if the command should be logged
564                    if(logCommand) {
565                        var componentVendor = componentControl.getComponentVendorByName(getComponentVendorName());
566
567                        if(componentVendor != null) {
568                            getCommandName();
569                            getParty(); // TODO: should only use if UserSession.IdentityVerifiedTime != null
570
571                            if(ControlDebugFlags.CheckCommandNameLength) {
572                                if(commandName.length() > 80) {
573                                    getLog().error("commandName length > 80 characters, " + commandName);
574                                    commandName = commandName.substring(0, 79);
575                                }
576                            }
577
578                            var command = commandControl.getCommandByName(componentVendor, commandName);
579
580                            if(command == null) {
581                                command = commandControl.createCommand(componentVendor, commandName, 1, party == null ? null : party.getPrimaryKey());
582                            }
583
584                            if(command != null) {
585                                var userVisitStatus = userControl.getUserVisitStatusForUpdate(userVisit);
586
587                                if(userVisitStatus != null) {
588                                    Integer userVisitCommandSequence = userVisitStatus.getUserVisitCommandSequence() + 1;
589                                    var hadSecurityErrors = securityResult == null ? null : securityResult.getHasMessages();
590                                    var hadValidationErrors = validationResult == null ? null : validationResult.getHasErrors();
591                                    var hasExecutionErrors = executionResult.getHasErrors();
592
593                                    userVisitStatus.setUserVisitCommandSequence(userVisitCommandSequence);
594                                    userVisitStatus.store();
595
596                                    userControl.createUserVisitCommand(userVisit, userVisitCommandSequence, party, command, session.getStartTime(),
597                                            System.currentTimeMillis(), hadSecurityErrors, hadValidationErrors, hasExecutionErrors);
598                                } else {
599                                    getLog().error("Command not logged, unknown userVisitStatus for " + userVisit.getPrimaryKey());
600                                }
601                            } else {
602                                getLog().error("Command not logged, unknown (and could not create) commandName = " + commandName);
603                            }
604                        } else {
605                            getLog().error("Command not logged, unknown componentVendorName = " + componentVendorName);
606                        }
607                    }
608                }
609            }
610        } finally {
611            teardownSession();
612            CommandScopeExtension.getCommandScopeContext().pop();
613        }
614
615        // The Session for this Thread must NOT be utilized by anything after teardownSession() has been called.
616        commandResult = new CommandResult<>(securityResult, validationResult, executionResult);
617
618        if(commandResult.hasSecurityMessages() || commandResult.hasValidationErrors()) {
619            getLog().info("commandResult = " + commandResult);
620        }
621
622        if(ControlDebugFlags.LogBaseCommands) {
623            if(commandResult.hasExecutionErrors()) {
624                log.info("<<< run(), returning executionResult = " + commandResult.getExecutionResult());
625            } else {
626                log.info("<<< run()");
627            }
628        }
629
630        return commandResult;
631    }
632
633    // --------------------------------------------------------------------------------
634    //   Security Utilities
635    // --------------------------------------------------------------------------------
636
637    protected boolean canSpecifyParty() {
638        var partyType = getPartyType();
639        var result = false; // Default to most restrictive result.
640
641        if(partyType != null) {
642            var partyTypeName = partyType.getPartyTypeName();
643
644            // Of PartyTypes that may login only EMPLOYEEs or UTILITYs may specify another Party. CUSTOMERs and
645            // VENDORs may not.
646            result = partyTypeName.equals(PartyTypes.EMPLOYEE.name())
647                    || partyTypeName.equals(PartyTypes.UTILITY.name());
648        }
649
650        return result;
651    }
652
653    protected SecurityResult selfOnly(PartySpec spec) {
654        var hasInsufficientSecurity = !canSpecifyParty() && spec.getPartyName() != null;
655
656        return hasInsufficientSecurity ? getInsufficientSecurityResult() : null;
657    }
658
659    protected SecurityResult getInsufficientSecurityResult() {
660        return new SecurityResult(new Messages().add(Messages.SECURITY_MESSAGE, new Message(SecurityMessages.InsufficientSecurity.name())));
661    }
662
663    // --------------------------------------------------------------------------------
664    //   Event Utilities
665    // --------------------------------------------------------------------------------
666
667    protected Event sendEvent(final BasePK basePK, final EventTypes eventType, final BasePK relatedBasePK,
668            final EventTypes relatedEventType, final BasePK createdByBasePK) {
669        var entityInstance = coreControl.getEntityInstanceByBasePK(basePK);
670        var relatedEntityInstance = relatedBasePK == null ? null : coreControl.getEntityInstanceByBasePK(relatedBasePK);
671        
672        return sendEvent(entityInstance, eventType, relatedEntityInstance, relatedEventType, createdByBasePK);
673    }
674    
675    protected Event sendEvent(final EntityInstance entityInstance, final EventTypes eventType, final BasePK relatedBasePK,
676            final EventTypes relatedEventType, final BasePK createdByBasePK) {
677        var relatedEntityInstance = relatedBasePK == null ? null : coreControl.getEntityInstanceByBasePK(relatedBasePK);
678
679        return sendEvent(entityInstance, eventType, relatedEntityInstance, relatedEventType, createdByBasePK);
680    }
681    
682    protected Event sendEvent(final EntityInstance entityInstance, final EventTypes eventType, final EntityInstance relatedEntityInstance,
683            final EventTypes relatedEventType, final BasePK createdByBasePK) {
684        Event event = null;
685        
686        if(createdByBasePK != null) {
687            event = eventControl.sendEvent(entityInstance, eventType, relatedEntityInstance, relatedEventType,
688                createdByBasePK);
689        }
690        
691        return event;
692    }
693    
694    // --------------------------------------------------------------------------------
695    //   Option Utilities
696    // --------------------------------------------------------------------------------
697
698    /** This should only be called an override of setupSession(). After that, TransferCaches may have cached knowledge
699     * that specific options were set.
700     * @param option The option to remove.
701     */
702    protected void removeOption(String option) {
703        session.getOptions().remove(option);
704    }
705
706    // --------------------------------------------------------------------------------
707    //   Transfer Property Utilities
708    // --------------------------------------------------------------------------------
709
710    /** This should only be called an override of setupSession(). After that, TransferCaches may have cached knowledge
711     * that specific properties were filtered.
712     * @param clazz The Class whose properties should be examined.
713     * @param property The property to remove.
714     */
715    protected void removeFilteredTransferProperty(Class<? extends BaseTransfer> clazz, String property) {
716        var transferProperties = session.getTransferProperties();
717
718        if(transferProperties != null) {
719            var properties = transferProperties.getProperties(clazz);
720
721            if(properties != null) {
722                properties.remove(property);
723            }
724        }
725    }
726    
727}