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}