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