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}