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