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