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