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