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}