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