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.transfer; 018 019import com.echothree.model.control.comment.common.transfer.CommentListWrapper; 020import com.echothree.model.control.comment.server.control.CommentControl; 021import com.echothree.model.control.core.common.transfer.EntityAliasTransfer; 022import com.echothree.model.control.core.common.transfer.EntityAttributeGroupTransfer; 023import com.echothree.model.control.core.common.transfer.LinkTypeTransfer; 024import com.echothree.model.control.core.server.control.CoreControl; 025import com.echothree.model.control.core.server.control.EntityAliasControl; 026import com.echothree.model.control.core.server.control.EntityAttributeGroupControl; 027import com.echothree.model.control.core.server.control.EntityInstanceControl; 028import com.echothree.model.control.core.server.control.LinkTypeControl; 029import com.echothree.model.control.rating.common.transfer.RatingListWrapper; 030import com.echothree.model.control.rating.server.control.RatingControl; 031import com.echothree.model.control.tag.common.transfer.TagScopeTransfer; 032import com.echothree.model.control.tag.server.control.TagControl; 033import com.echothree.model.control.user.server.control.UserControl; 034import com.echothree.model.control.workeffort.server.control.WorkEffortControl; 035import com.echothree.model.data.accounting.server.entity.Currency; 036import com.echothree.model.data.comment.server.entity.CommentType; 037import com.echothree.model.data.core.server.entity.EntityInstance; 038import com.echothree.model.data.party.common.pk.PartyPK; 039import com.echothree.model.data.party.server.entity.DateTimeFormat; 040import com.echothree.model.data.party.server.entity.Language; 041import com.echothree.model.data.party.server.entity.Party; 042import com.echothree.model.data.party.server.entity.TimeZone; 043import com.echothree.model.data.rating.server.entity.RatingType; 044import com.echothree.model.data.uom.server.entity.UnitOfMeasureKind; 045import com.echothree.model.data.user.server.entity.UserVisit; 046import com.echothree.util.common.exception.TransferOptionDependencyException; 047import com.echothree.util.common.string.Inet4AddressUtils; 048import com.echothree.util.common.transfer.BaseOptions; 049import com.echothree.util.common.transfer.BaseTransfer; 050import com.echothree.util.common.transfer.ListWrapper; 051import com.echothree.util.common.transfer.MapWrapper; 052import com.echothree.util.server.persistence.BaseEntity; 053import com.echothree.util.server.persistence.Session; 054import com.echothree.util.server.persistence.ThreadSession; 055import com.echothree.util.server.string.DateUtils; 056import com.echothree.util.server.string.PercentUtils; 057import com.echothree.util.server.string.UnitOfMeasureUtils; 058import java.util.HashMap; 059import java.util.Map; 060import javax.inject.Inject; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064public abstract class BaseTransferCache<K extends BaseEntity, V extends BaseTransfer> { 065 066 protected final Logger log = LoggerFactory.getLogger(this.getClass()); 067 068 @Inject 069 protected CommentControl commentControl; 070 071 @Inject 072 protected CoreControl coreControl; 073 074 @Inject 075 protected EntityAliasControl entityAliasControl; 076 077 @Inject 078 protected EntityAttributeGroupControl entityAttributeGroupControl; 079 080 @Inject 081 protected EntityInstanceControl entityInstanceControl; 082 083 @Inject 084 protected LinkTypeControl linkTypeControl; 085 086 @Inject 087 protected RatingControl ratingControl; 088 089 @Inject 090 protected TagControl tagControl; 091 092 @Inject 093 protected UserControl userControl; 094 095 @Inject 096 protected WorkEffortControl workEffortControl; 097 098 protected Session session; 099 protected Map<K, V> transferCache; 100 101 private Party party; 102 private Language language; 103 private Currency currency; 104 private TimeZone timeZone; 105 private DateTimeFormat dateTimeFormat; 106 107 boolean includeEntityInstance; 108 boolean includeEntityAppearance; 109 boolean includeEntityVisit; 110 boolean includeNames; 111 boolean includeUuid; 112 boolean includeEntityAliases; 113 boolean includeEntityAttributeGroups; 114 boolean includeTagScopes; 115 boolean includeSourceLinkTypes; 116 boolean includeTargetLinkTypes; 117 118 /** Creates a new instance of BaseTransferCache */ 119 protected BaseTransferCache() { 120 session = ThreadSession.currentSession(); 121 transferCache = new HashMap<>(); 122 123 var options = session.getOptions(); 124 if(options != null) { 125 includeEntityAliases = options.contains(BaseOptions.BaseIncludeEntityAliases); 126 includeEntityAttributeGroups = options.contains(BaseOptions.BaseIncludeEntityAttributeGroups); 127 includeTagScopes = options.contains(BaseOptions.BaseIncludeTagScopes); 128 includeSourceLinkTypes = options.contains(BaseOptions.BaseIncludeSourceLinkTypes); 129 includeTargetLinkTypes = options.contains(BaseOptions.BaseIncludeTargetLinkTypes); 130 } 131 } 132 133 protected void put(final UserVisit userVisit, final K key, final V value, final EntityInstance entityInstance) { 134 transferCache.put(key, value); 135 136 if(includeEntityInstance) { 137 setupEntityInstance(userVisit, key, entityInstance, value); 138 } 139 } 140 141 protected void put(final UserVisit userVisit, final K key, final V value) { 142 put(userVisit, key, value, null); 143 } 144 145 protected V get(Object key) { 146 return transferCache.get(key); 147 } 148 149 protected PartyPK getPartyPK(final UserVisit userVisit) { 150 if(party == null) { 151 getParty(userVisit); 152 } 153 154 return party == null? null: party.getPrimaryKey(); 155 } 156 157 protected Party getParty(final UserVisit userVisit) { 158 if(party == null) { 159 party = userControl.getPartyFromUserVisit(userVisit); 160 } 161 162 return party; 163 } 164 165 protected Language getLanguage(final UserVisit userVisit) { 166 if(language == null) { 167 language = userControl.getPreferredLanguageFromUserVisit(userVisit); 168 } 169 170 return language; 171 } 172 173 protected Currency getCurrency(final UserVisit userVisit) { 174 if(currency == null) { 175 currency = userControl.getPreferredCurrencyFromUserVisit(userVisit); 176 } 177 178 return currency; 179 } 180 181 protected TimeZone getTimeZone(final UserVisit userVisit) { 182 if(timeZone == null) { 183 timeZone = userControl.getPreferredTimeZoneFromUserVisit(userVisit); 184 } 185 186 return timeZone; 187 } 188 189 protected DateTimeFormat getDateTimeFormat(final UserVisit userVisit) { 190 if(dateTimeFormat == null) { 191 dateTimeFormat = userControl.getPreferredDateTimeFormatFromUserVisit(userVisit); 192 } 193 194 return dateTimeFormat; 195 } 196 197 protected String formatTypicalDateTime(final UserVisit userVisit, Long time) { 198 return DateUtils.getInstance().formatTypicalDateTime(userVisit, time); 199 } 200 201 protected String formatFractionalPercent(Integer percent) { 202 return PercentUtils.getInstance().formatFractionalPercent(percent); 203 } 204 205 protected String formatInet4Address(Integer inet4Address) { 206 return Inet4AddressUtils.getInstance().formatInet4Address(inet4Address); 207 } 208 209 protected String formatUnitOfMeasure(final UserVisit userVisit, UnitOfMeasureKind unitOfMeasureKind, Long measure) { 210 return UnitOfMeasureUtils.getInstance().formatUnitOfMeasure(userVisit, unitOfMeasureKind, measure); 211 } 212 213 /** 214 * Returns the includeEntityAliases. 215 * @return the includeEntityAliases 216 */ 217 protected boolean getIncludeEntityAliases() { 218 return includeEntityAliases; 219 } 220 221 /** 222 * Sets the includeEntityAliases. 223 * @param includeEntityAliases the includeEntityAliases to set 224 */ 225 protected void setIncludeEntityAliases(boolean includeEntityAliases) { 226 this.includeEntityAliases = includeEntityAliases; 227 } 228 229 protected void setupEntityAliases(final UserVisit userVisit, EntityInstance entityInstance, V transfer) { 230 var entityAliasTransfers = entityAliasControl.getEntityAliasTransfersByEntityInstance(userVisit, entityInstance); 231 var mapWrapper = new MapWrapper<EntityAliasTransfer>(entityAliasTransfers.size()); 232 233 entityAliasTransfers.forEach((entityAliasTransfer) -> { 234 mapWrapper.put(entityAliasTransfer.getEntityAliasType().getEntityAliasTypeName(), entityAliasTransfer); 235 }); 236 237 transfer.setEntityAliases(mapWrapper); 238 } 239 240 /** 241 * Returns the includeEntityAttributeGroups. 242 * @return the includeEntityAttributeGroups 243 */ 244 protected boolean getIncludeEntityAttributeGroups() { 245 return includeEntityAttributeGroups; 246 } 247 248 /** 249 * Sets the includeEntityAttributeGroups. 250 * @param includeEntityAttributeGroups the includeEntityAttributeGroups to set 251 */ 252 protected void setIncludeEntityAttributeGroups(boolean includeEntityAttributeGroups) { 253 this.includeEntityAttributeGroups = includeEntityAttributeGroups; 254 } 255 256 protected void setupEntityAttributeGroups(final UserVisit userVisit, EntityInstance entityInstance, V transfer) { 257 var entityAttributeGroupTransfers = entityAttributeGroupControl.getEntityAttributeGroupTransfersByEntityType(userVisit, entityInstance.getEntityType(), entityInstance); 258 var mapWrapper = new MapWrapper<EntityAttributeGroupTransfer>(entityAttributeGroupTransfers.size()); 259 260 entityAttributeGroupTransfers.forEach((entityAttributeGroupTransfer) -> { 261 mapWrapper.put(entityAttributeGroupTransfer.getEntityAttributeGroupName(), entityAttributeGroupTransfer); 262 }); 263 264 transfer.setEntityAttributeGroups(mapWrapper); 265 } 266 267 /** 268 * Returns the includeTagScopes. 269 * @return the includeTagScopes 270 */ 271 protected boolean getIncludeTagScopes() { 272 return includeTagScopes; 273 } 274 275 /** 276 * Sets the includeTagScopes. 277 * @param includeTagScopes the includeTagScopes to set 278 */ 279 protected void setIncludeTagScopes(boolean includeTagScopes) { 280 this.includeTagScopes = includeTagScopes; 281 } 282 283 protected void setupTagScopes(final UserVisit userVisit, EntityInstance entityInstance, V transfer) { 284 var tagScopes = tagControl.getTagScopesByEntityType(entityInstance.getEntityType()); 285 var mapWrapper = new MapWrapper<TagScopeTransfer>(tagScopes.size()); 286 287 tagScopes.stream().map((tagScope) -> { 288 // We get a copy of the TagScopeTransfer since we'll be modifying a field on it. Because there may be multiple instances of the 289 // TransferObject that we're building at this point, they may each have their own List of Tags within a given TagScope, so we do 290 // not want the tags property to be shared among all of them. 291 var tagScopeTransfer = tagControl.getTagScopeTransfer(userVisit, tagScope).copy(); 292 tagScopeTransfer.setTags(new ListWrapper<>(tagControl.getTagTransfersByTagScopeAndEntityInstance(userVisit, tagScope, entityInstance))); 293 return tagScopeTransfer; 294 }).forEach((tagScopeTransfer) -> { 295 mapWrapper.put(tagScopeTransfer.getTagScopeName(), tagScopeTransfer); 296 }); 297 298 transfer.setTagScopes(mapWrapper); 299 } 300 301 protected void setIncludeSourceLinkTypes(boolean includeSourceLinkTypes) { 302 this.includeSourceLinkTypes = includeSourceLinkTypes; 303 } 304 305 protected void setIncludeTargetLinkTypes(boolean includeTargetLinkTypes) { 306 this.includeTargetLinkTypes = includeTargetLinkTypes; 307 } 308 309 protected void setupLinkTypes(UserVisit userVisit, EntityInstance entityInstance, V transfer, boolean asSource) { 310 var linkTypeTransfers = asSource ? linkTypeControl.getLinkTypeTransfersBySourceEntityInstance(userVisit, entityInstance) 311 : linkTypeControl.getLinkTypeTransfersByTargetEntityInstance(userVisit, entityInstance); 312 var mapWrapper = new MapWrapper<LinkTypeTransfer>(linkTypeTransfers.size()); 313 314 linkTypeTransfers.forEach(linkTypeTransfer -> 315 mapWrapper.put(linkTypeTransfer.getLinkTypeName(), linkTypeTransfer) 316 ); 317 318 if(asSource) { 319 transfer.setSourceLinkTypes(mapWrapper); 320 } else { 321 transfer.setTargetLinkTypes(mapWrapper); 322 } 323 } 324 325 /** 326 * Returns the includeEntityInstance. 327 * @return the includeEntityInstance 328 */ 329 protected boolean getIncludeEntityInstance() { 330 return includeEntityInstance; 331 } 332 333 /** 334 * Sets the setupBaseTransfer. 335 * @param includeEntityInstance the setupBaseTransfer to set 336 */ 337 protected void setIncludeEntityInstance(boolean includeEntityInstance) { 338 this.includeEntityInstance = includeEntityInstance; 339 } 340 341 /** 342 * Returns the includeEntityAppearance. 343 * @return the includeEntityAppearance 344 */ 345 protected boolean getIncludeEntityAppearance() { 346 return includeEntityAppearance; 347 } 348 349 /** 350 * Sets the includeEntityAppearance. 351 * @param includeEntityAppearance the includeEntityAppearance to set 352 */ 353 protected void setIncludeEntityAppearance(boolean includeEntityAppearance) { 354 this.includeEntityAppearance = includeEntityAppearance; 355 } 356 357 /** 358 * Returns the includeEntityVisit. 359 * @return the includeEntityVisit 360 */ 361 protected boolean getIncludeEntityVisit() { 362 return includeEntityVisit; 363 } 364 365 /** 366 * Sets the includeEntityVisit. 367 * @param includeEntityVisit the includeEntityVisit to set 368 */ 369 protected void setIncludeEntityVisit(boolean includeEntityVisit) { 370 this.includeEntityVisit = includeEntityVisit; 371 } 372 373 /** 374 * Returns the includeNames. 375 * @return the includeNames 376 */ 377 protected boolean getIncludeNames() { 378 return includeNames; 379 } 380 381 /** 382 * Sets the includeNames. 383 * @param includeNames the includeNames to set 384 */ 385 protected void setIncludeNames(boolean includeNames) { 386 this.includeNames = includeNames; 387 } 388 389 /** 390 * Returns the includeUuid. 391 * @return the includeUuid 392 */ 393 protected boolean getIncludeUuid() { 394 return includeUuid; 395 } 396 397 /** 398 * Sets the includeUuid. 399 * @param includeUuid the includeUuid to set 400 */ 401 protected void setIncludeUuid(boolean includeUuid) { 402 this.includeUuid = includeUuid; 403 } 404 405 protected void setupEntityInstance(final UserVisit userVisit, final K baseEntity, EntityInstance entityInstance, final V transfer) { 406 if(entityInstance == null) { 407 entityInstance = entityInstanceControl.getEntityInstanceByBasePK(baseEntity.getPrimaryKey()); 408 } 409 410 // Check to make sure entityInstance is not null. This may happen in a case where a non-versioned entity was 411 // converted to a versioned one. 412 if(entityInstance != null) { 413 transfer.setEntityInstance(entityInstanceControl.getEntityInstanceTransfer(userVisit, entityInstance, includeEntityAppearance, 414 includeEntityVisit, includeNames, includeUuid)); 415 416 if(includeEntityAliases || includeEntityAttributeGroups || includeTagScopes || includeSourceLinkTypes || includeTargetLinkTypes) { 417 if(includeEntityAliases) { 418 setupEntityAliases(userVisit, entityInstance, transfer); 419 } 420 421 if(includeEntityAttributeGroups) { 422 setupEntityAttributeGroups(userVisit, entityInstance, transfer); 423 } 424 425 if(includeTagScopes) { 426 setupTagScopes(userVisit, entityInstance, transfer); 427 } 428 429 if(includeSourceLinkTypes) { 430 setupLinkTypes(userVisit, entityInstance, transfer, true); 431 } 432 433 if(includeTargetLinkTypes) { 434 setupLinkTypes(userVisit, entityInstance, transfer, false); 435 } 436 } 437 } 438 } 439 440 protected EntityInstance setupAllCommentTypes(final UserVisit userVisit, final K commentedEntity, EntityInstance commentedEntityInstance, 441 final V transfer) { 442 var entityType = commentedEntityInstance.getEntityType(); 443 var commentTypes = commentControl.getCommentTypes(entityType); 444 445 for(var commentType: commentTypes) { 446 setupComments(userVisit, commentedEntity, commentedEntityInstance, transfer, commentType); 447 } 448 449 return commentedEntityInstance; 450 } 451 452 protected EntityInstance setupComments(final UserVisit userVisit, final K commentedEntity, EntityInstance commentedEntityInstance, 453 final V transfer, final String commentTypeName) { 454 if(commentedEntityInstance == null) { 455 commentedEntityInstance = entityInstanceControl.getEntityInstanceByBasePK(commentedEntity.getPrimaryKey()); 456 } 457 458 var commentType = commentControl.getCommentTypeByName(commentedEntityInstance.getEntityType(), commentTypeName); 459 460 return setupComments(userVisit, commentedEntity, commentedEntityInstance, transfer, commentType); 461 } 462 463 protected EntityInstance setupComments(final UserVisit userVisit, final K commentedEntity, EntityInstance commentedEntityInstance, 464 final V transfer, final CommentType commentType) { 465 if(commentedEntityInstance == null) { 466 commentedEntityInstance = entityInstanceControl.getEntityInstanceByBasePK(commentedEntity.getPrimaryKey()); 467 } 468 469 var commentTypeTransfer = commentControl.getCommentTypeTransfer(userVisit, commentType); 470 var commentTypeName = commentTypeTransfer.getCommentTypeName(); 471 var commentTransfers = commentControl.getCommentTransfersByCommentedEntityInstanceAndCommentType(userVisit, commentedEntityInstance, commentType); 472 var commentListWrapper = new CommentListWrapper(commentTypeTransfer, commentTransfers); 473 transfer.addComments(commentTypeName, commentListWrapper); 474 475 return commentedEntityInstance; 476 } 477 478 protected EntityInstance setupAllRatingTypes(final UserVisit userVisit, final K ratedEntity, EntityInstance ratedEntityInstance, 479 final V transfer) { 480 var entityType = ratedEntityInstance.getEntityType(); 481 var ratingTypes = ratingControl.getRatingTypes(entityType); 482 483 for(var ratingType: ratingTypes) { 484 setupRatings(userVisit, ratedEntity, ratedEntityInstance, transfer, ratingType); 485 } 486 487 return ratedEntityInstance; 488 } 489 490 protected EntityInstance setupRatings(final UserVisit userVisit, final K ratedEntity, EntityInstance ratedEntityInstance, 491 final V transfer, final String ratingTypeName) { 492 if(ratedEntityInstance == null) { 493 ratedEntityInstance = entityInstanceControl.getEntityInstanceByBasePK(ratedEntity.getPrimaryKey()); 494 } 495 496 var ratingType = ratingControl.getRatingTypeByName(ratedEntityInstance.getEntityType(), ratingTypeName); 497 498 return setupRatings(userVisit, ratedEntity, ratedEntityInstance, transfer, ratingType); 499 } 500 501 protected EntityInstance setupRatings(final UserVisit userVisit, final K ratedEntity, EntityInstance ratedEntityInstance, 502 final V transfer, final RatingType ratingType) { 503 if(ratedEntityInstance == null) { 504 ratedEntityInstance = entityInstanceControl.getEntityInstanceByBasePK(ratedEntity.getPrimaryKey()); 505 } 506 507 var ratingTypeTransfer = ratingControl.getRatingTypeTransfer(userVisit, ratingType); 508 var ratingTypeName = ratingTypeTransfer.getRatingTypeName(); 509 var ratingTransfers = ratingControl.getRatingTransfersByRatedEntityInstanceAndRatingType(userVisit, ratedEntityInstance, ratingType); 510 var ratingListWrapper = new RatingListWrapper(ratingTypeTransfer, ratingTransfers); 511 transfer.addRatings(ratingTypeName, ratingListWrapper); 512 513 return ratedEntityInstance; 514 } 515 516 protected EntityInstance setupOwnedWorkEfforts(final UserVisit userVisit, final K baseEntity, EntityInstance owningEntityInstance, final V transfer) { 517 if(owningEntityInstance == null) { 518 owningEntityInstance = entityInstanceControl.getEntityInstanceByBasePK(baseEntity.getPrimaryKey()); 519 } 520 521 for(var workEffort: workEffortControl.getWorkEffortTransfersByOwningEntityInstance(userVisit, owningEntityInstance)) { 522 transfer.addOwnedWorkEffort(workEffort.getWorkEffortScope().getWorkEffortType().getWorkEffortTypeName(), workEffort); 523 } 524 525 return owningEntityInstance; 526 } 527 528 protected void verifyOptionDependency(String dependentOption, String dependsOnOption) { 529 var options = session.getOptions(); 530 531 if(!options.contains(dependsOnOption)) { 532 // Throwing an Exception for this seems harsh, but failure to meet the requirements could result in an NPE or other Exceptions. 533 throw new TransferOptionDependencyException(dependentOption + " requires that " + dependsOnOption + " be set as well"); 534 } 535 } 536 537}