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