001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 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.rating.common.transfer.RatingListWrapper;
025import com.echothree.model.control.rating.server.control.RatingControl;
026import com.echothree.model.control.tag.common.transfer.TagScopeTransfer;
027import com.echothree.model.control.tag.server.control.TagControl;
028import com.echothree.model.control.uom.server.control.UomControl;
029import com.echothree.model.control.user.server.control.UserControl;
030import com.echothree.model.control.workeffort.common.transfer.WorkEffortTransfer;
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.tag.server.entity.TagScope;
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.List;
058import java.util.Map;
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    protected UserVisit userVisit;
067    protected Session session;
068    protected Map<K, V> transferCache;
069    
070    private UomControl uomControl;
071    private UserControl userControl;
072    private Party party;
073    private Language language;
074    private Currency currency;
075    private TimeZone timeZone;
076    private DateTimeFormat dateTimeFormat;
077
078    boolean includeEntityInstance;
079    boolean includeEntityAppearance;
080    boolean includeEntityVisit;
081    boolean includeNames;
082    boolean includeKey;
083    boolean includeGuid;
084    boolean includeUlid;
085    boolean includeEntityAliasTypes;
086    boolean includeEntityAttributeGroups;
087    boolean includeTagScopes;
088    
089    /** Creates a new instance of BaseTransferCache */
090    protected BaseTransferCache(UserVisit userVisit) {
091        this.userVisit = userVisit;
092        
093        session = ThreadSession.currentSession();
094        transferCache = new HashMap<>();
095        
096        var options = session.getOptions();
097        if(options != null) {
098            includeEntityAliasTypes = options.contains(BaseOptions.BaseIncludeEntityAliasTypes);
099            includeEntityAttributeGroups = options.contains(BaseOptions.BaseIncludeEntityAttributeGroups);
100            includeTagScopes = options.contains(BaseOptions.BaseIncludeTagScopes);
101        }
102    }
103    
104    protected Log getLog() {
105        if(log == null) {
106            log = LogFactory.getLog(this.getClass());
107        }
108        
109        return log;
110    }
111    
112    protected void put(final K key, final V value, final EntityInstance entityInstance) {
113        transferCache.put(key, value);
114
115        if(includeEntityInstance) {
116            setupEntityInstance(key, entityInstance, value);
117        }
118    }
119
120    protected void put(final K key, final V value) {
121        put(key, value, null);
122    }
123
124    protected V get(Object key) {
125        return transferCache.get(key);
126    }
127    
128    protected UomControl getUomControl() {
129        if(uomControl == null) {
130            uomControl = Session.getModelController(UomControl.class);
131        }
132        
133        return uomControl;
134    }
135    
136    protected UserControl getUserControl() {
137        if(userControl == null) {
138            userControl = Session.getModelController(UserControl.class);
139        }
140        
141        return userControl;
142    }
143
144    protected PartyPK getPartyPK() {
145        if(party == null) {
146            getParty();
147        }
148
149        return party == null? null: party.getPrimaryKey();
150    }
151
152    protected Party getParty() {
153        if(party == null) {
154            party = getUserControl().getPartyFromUserVisit(userVisit);
155        }
156
157        return party;
158    }
159
160    protected Language getLanguage() {
161        if(language == null) {
162            language = getUserControl().getPreferredLanguageFromUserVisit(userVisit);
163        }
164        
165        return language;
166    }
167    
168    protected Currency getCurrency() {
169        if(currency == null) {
170            currency = getUserControl().getPreferredCurrencyFromUserVisit(userVisit);
171        }
172        
173        return currency;
174    }
175    
176    protected TimeZone getTimeZone() {
177        if(timeZone == null) {
178            timeZone = getUserControl().getPreferredTimeZoneFromUserVisit(userVisit);
179        }
180        
181        return timeZone;
182    }
183    
184    protected DateTimeFormat getDateTimeFormat() {
185        if(dateTimeFormat == null) {
186            dateTimeFormat = getUserControl().getPreferredDateTimeFormatFromUserVisit(userVisit);
187        }
188        
189        return dateTimeFormat;
190    }
191
192    protected String formatTypicalDateTime(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(UnitOfMeasureKind unitOfMeasureKind, Long measure) {
205        return UnitOfMeasureUtils.getInstance().formatUnitOfMeasure(userVisit, unitOfMeasureKind, measure);
206    }
207
208    /**
209     * Returns the includeEntityAliasTypes.
210     * @return the includeEntityAliasTypes
211     */
212    protected boolean getIncludeEntityAliasTypes() {
213        return includeEntityAliasTypes;
214    }
215
216    /**
217     * Sets the includeEntityAliasTypes.
218     * @param includeEntityAliasTypes the includeEntityAliasTypes to set
219     */
220    protected void setIncludeEntityAliasTypes(boolean includeEntityAliasTypes) {
221        this.includeEntityAliasTypes = includeEntityAliasTypes;
222    }
223
224    protected void setupEntityAliasTypes(CoreControl coreControl, EntityInstance entityInstance, V transfer) {
225        var entityAliasTypeTransfers = coreControl.getEntityAliasTypeTransfersByEntityType(userVisit, entityInstance.getEntityType(), entityInstance);
226        var mapWrapper = new MapWrapper<EntityAliasTypeTransfer>(entityAliasTypeTransfers.size());
227
228        entityAliasTypeTransfers.forEach((entityAliasTypeTransfer) -> {
229            mapWrapper.put(entityAliasTypeTransfer.getEntityAliasTypeName(), entityAliasTypeTransfer);
230        });
231
232        transfer.setEntityAliasTypes(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(CoreControl coreControl, EntityInstance entityInstance, V transfer) {
252        List<EntityAttributeGroupTransfer> entityAttributeGroupTransfers = coreControl.getEntityAttributeGroupTransfersByEntityType(userVisit, entityInstance.getEntityType(), entityInstance);
253        MapWrapper<EntityAttributeGroupTransfer> mapWrapper = new MapWrapper<>(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(CoreControl coreControl, EntityInstance entityInstance, V transfer) {
279        TagControl tagControl = Session.getModelController(TagControl.class);
280        List<TagScope> tagScopes = tagControl.getTagScopesByEntityType(entityInstance.getEntityType());
281        MapWrapper<TagScopeTransfer> mapWrapper = new MapWrapper<>(tagScopes.size());
282
283        tagScopes.stream().map((tagScope) -> {
284            // We get a copy of the TagScopeTransfer since we'll be modifying a field on it. Because there may be multiple instances of the
285            // TransferObject that we're building at this point, they may each have their own List of Tags within a given TagScope, so we do
286            // not want the tags property to be shared among all of them.
287            TagScopeTransfer tagScopeTransfer = tagControl.getTagScopeTransfer(userVisit, tagScope).copy();
288            tagScopeTransfer.setTags(new ListWrapper<>(tagControl.getTagTransfersByTagScopeAndEntityInstance(userVisit, tagScope, entityInstance)));
289            return tagScopeTransfer;
290        }).forEach((tagScopeTransfer) -> {
291            mapWrapper.put(tagScopeTransfer.getTagScopeName(), tagScopeTransfer);
292        });
293
294        transfer.setTagScopes(mapWrapper);
295    }
296
297
298    /**
299     * Returns the includeEntityInstance.
300     * @return the includeEntityInstance
301     */
302    protected boolean getIncludeEntityInstance() {
303        return includeEntityInstance;
304    }
305
306    /**
307     * Sets the setupBaseTransfer.
308     * @param includeEntityInstance the setupBaseTransfer to set
309     */
310    protected void setIncludeEntityInstance(boolean includeEntityInstance) {
311        this.includeEntityInstance = includeEntityInstance;
312    }
313
314    /**
315     * Returns the includeEntityAppearance.
316     * @return the includeEntityAppearance
317     */
318    protected boolean getIncludeEntityAppearance() {
319        return includeEntityAppearance;
320    }
321
322    /**
323     * Sets the includeEntityAppearance.
324     * @param includeEntityAppearance the includeEntityAppearance to set
325     */
326    protected void setIncludeEntityAppearance(boolean includeEntityAppearance) {
327        this.includeEntityAppearance = includeEntityAppearance;
328    }
329
330    /**
331     * Returns the includeEntityVisit.
332     * @return the includeEntityVisit
333     */
334    protected boolean getIncludeEntityVisit() {
335        return includeEntityVisit;
336    }
337
338    /**
339     * Sets the includeEntityVisit.
340     * @param includeEntityVisit the includeEntityVisit to set
341     */
342    protected void setIncludeEntityVisit(boolean includeEntityVisit) {
343        this.includeEntityVisit = includeEntityVisit;
344    }
345
346    /**
347     * Returns the includeNames.
348     * @return the includeNames
349     */
350    protected boolean getIncludeNames() {
351        return includeNames;
352    }
353
354    /**
355     * Sets the includeNames.
356     * @param includeNames the includeNames to set
357     */
358    protected void setIncludeNames(boolean includeNames) {
359        this.includeNames = includeNames;
360    }
361
362    /**
363     * Returns the includeKey.
364     * @return the includeKey
365     */
366    protected boolean getIncludeKey() {
367        return includeKey;
368    }
369
370    /**
371     * Sets the includeKey.
372     * @param includeKey the includeKey to set
373     */
374    protected void setIncludeKey(boolean includeKey) {
375        this.includeKey = includeKey;
376    }
377
378    /**
379     * Returns the includeGuid.
380     * @return the includeGuid
381     */
382    protected boolean getIncludeGuid() {
383        return includeGuid;
384    }
385
386    /**
387     * Sets the includeGuid.
388     * @param includeGuid the includeGuid to set
389     */
390    protected void setIncludeGuid(boolean includeGuid) {
391        this.includeGuid = includeGuid;
392    }
393    
394    /**
395     * Sets the includeUlid.
396     * @param includeUlid the includeUlid to set
397     */
398    protected void setIncludeUlid(boolean includeUlid) {
399        this.includeUlid = includeUlid;
400    }
401    
402    protected void setupEntityInstance(final K baseEntity, EntityInstance entityInstance, final V transfer) {
403        CoreControl coreControl = Session.getModelController(CoreControl.class);
404        
405        if(entityInstance == null) {
406            entityInstance = coreControl.getEntityInstanceByBasePK(baseEntity.getPrimaryKey());
407        }
408
409        // Check to make sure entityInstance is not null. This may happen in a case where a non-versioned entity was
410        // converted to a versioned one.
411        if(entityInstance != null) {
412            transfer.setEntityInstance(coreControl.getEntityInstanceTransfer(userVisit, entityInstance, includeEntityAppearance,
413                    includeEntityVisit, includeNames, includeKey, includeGuid, includeUlid));
414
415            if(includeEntityAliasTypes || includeEntityAttributeGroups || includeTagScopes) {
416                if(includeEntityAliasTypes) {
417                    setupEntityAliasTypes(coreControl, entityInstance, transfer);
418                }
419
420                if(includeEntityAttributeGroups) {
421                    setupEntityAttributeGroups(coreControl, entityInstance, transfer);
422                }
423
424                if(includeTagScopes) {
425                    setupTagScopes(coreControl, entityInstance, transfer);
426                }
427            }
428        }
429    }
430
431    protected EntityInstance setupComments(final K commentedEntity, EntityInstance commentedEntityInstance, final V transfer, final String commentTypeName) {
432        CommentControl commentControl = Session.getModelController(CommentControl.class);
433        
434        if(commentedEntityInstance == null) {
435            CoreControl coreControl = Session.getModelController(CoreControl.class);
436            
437            commentedEntityInstance = coreControl.getEntityInstanceByBasePK(commentedEntity.getPrimaryKey());
438        }
439        
440        CommentType commentType = commentControl.getCommentTypeByName(commentedEntityInstance.getEntityType(), commentTypeName);
441        transfer.addComments(commentTypeName, new CommentListWrapper(commentControl.getCommentTypeTransfer(userVisit, commentType),
442                commentControl.getCommentTransfersByCommentedEntityInstanceAndCommentType(userVisit, commentedEntityInstance, commentType)));
443        
444        return commentedEntityInstance;
445    }
446
447    protected EntityInstance setupRatings(final K ratedEntity, EntityInstance ratedEntityInstance, final V transfer, final String ratingTypeName) {
448        RatingControl ratingControl = Session.getModelController(RatingControl.class);
449        
450        if(ratedEntityInstance == null) {
451            CoreControl coreControl = Session.getModelController(CoreControl.class);
452            
453            ratedEntityInstance = coreControl.getEntityInstanceByBasePK(ratedEntity.getPrimaryKey());
454        }
455        
456        RatingType ratingType = ratingControl.getRatingTypeByName(ratedEntityInstance.getEntityType(), ratingTypeName);
457        transfer.addRatings(ratingTypeName, new RatingListWrapper(ratingControl.getRatingTypeTransfer(userVisit, ratingType),
458                ratingControl.getRatingTransfersByRatedEntityInstanceAndRatingType(userVisit, ratedEntityInstance, ratingType)));
459
460        return ratedEntityInstance;
461    }
462
463    protected EntityInstance setupOwnedWorkEfforts(final K baseEntity, EntityInstance owningEntityInstance, final V transfer) {
464        WorkEffortControl workEffortControl = Session.getModelController(WorkEffortControl.class);
465        
466        if(owningEntityInstance == null) {
467            CoreControl coreControl = Session.getModelController(CoreControl.class);
468            
469            owningEntityInstance = coreControl.getEntityInstanceByBasePK(baseEntity.getPrimaryKey());
470        }
471        
472        for(WorkEffortTransfer workEffort: workEffortControl.getWorkEffortTransfersByOwningEntityInstance(userVisit, owningEntityInstance)) {
473            transfer.addOwnedWorkEffort(workEffort.getWorkEffortScope().getWorkEffortType().getWorkEffortTypeName(), workEffort);
474        }
475
476        return owningEntityInstance;
477    }
478    
479    protected void verifyOptionDependency(String dependentOption, String dependsOnOption) {
480        var options = session.getOptions();
481        
482        if(!options.contains(dependsOnOption)) {
483            // Throwing an Exception for this seems harsh, but failure to meet the requirements could result in an NPE or other Exceptions.
484            throw new TransferOptionDependencyException(dependentOption + " requires that " + dependsOnOption + " be set as well");
485        }
486    }
487    
488}