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.model.control.search.server.logic;
018
019import com.echothree.model.control.core.common.EntityAttributeTypes;
020import com.echothree.model.control.core.common.exception.InvalidEntityAttributeTypeException;
021import com.echothree.model.control.core.server.control.CoreControl;
022import com.echothree.model.control.search.common.transfer.UserVisitSearchFacetIntegerTransfer;
023import com.echothree.model.control.search.common.transfer.UserVisitSearchFacetListItemTransfer;
024import com.echothree.model.control.search.common.transfer.UserVisitSearchFacetLongTransfer;
025import com.echothree.model.control.search.common.transfer.UserVisitSearchFacetTransfer;
026import com.echothree.model.control.search.server.database.EntityIntegerAttributeFacetQuery;
027import com.echothree.model.control.search.server.database.EntityListItemAttributeFacetQuery;
028import com.echothree.model.control.search.server.database.EntityLongAttributeFacetQuery;
029import com.echothree.model.control.search.server.database.EntityMultipleListItemAttributeFacetQuery;
030import com.echothree.model.control.search.server.graphql.UserVisitSearchFacetIntegerObject;
031import com.echothree.model.control.search.server.graphql.UserVisitSearchFacetListItemObject;
032import com.echothree.model.control.search.server.graphql.UserVisitSearchFacetLongObject;
033import com.echothree.model.control.search.server.graphql.UserVisitSearchFacetObject;
034import com.echothree.model.data.core.server.entity.EntityAttribute;
035import com.echothree.model.data.core.server.entity.EntityIntegerRangeDetail;
036import com.echothree.model.data.core.server.entity.EntityLongRangeDetail;
037import com.echothree.model.data.search.server.entity.UserVisitSearch;
038import com.echothree.util.common.message.ExecutionErrors;
039import com.echothree.util.common.transfer.ListWrapper;
040import com.echothree.util.server.control.BaseLogic;
041import com.echothree.util.server.message.ExecutionErrorAccumulator;
042import com.echothree.util.server.persistence.Session;
043import java.util.ArrayList;
044import java.util.Iterator;
045import java.util.List;
046
047public class UserVisitSearchFacetLogic
048        extends BaseLogic {
049
050    private UserVisitSearchFacetLogic() {
051        super();
052    }
053
054    private static class UserVisitSearchFacetLogicHolder {
055        static UserVisitSearchFacetLogic instance = new UserVisitSearchFacetLogic();
056    }
057
058    public static UserVisitSearchFacetLogic getInstance() {
059        return UserVisitSearchFacetLogicHolder.instance;
060    }
061    
062    private List<EntityIntegerRangeDetail> getEntityIntegerRangeDetails(final EntityAttribute entityAttribute) {
063        final var coreControl = Session.getModelController(CoreControl.class);
064        final var entityIntegerRanges = coreControl.getEntityIntegerRanges(entityAttribute);
065        final var entityIntegerRangeDetails = new ArrayList<EntityIntegerRangeDetail>(entityIntegerRanges.size());
066        
067        entityIntegerRanges.forEach((entityIntegerRange) -> {
068            entityIntegerRangeDetails.add(entityIntegerRange.getLastDetail());
069        });
070        
071        return entityIntegerRangeDetails;
072    }
073    
074    private List<EntityLongRangeDetail> getEntityLongRangeDetails(final EntityAttribute entityAttribute) {
075        final var coreControl = Session.getModelController(CoreControl.class);
076        final var entityLongRanges = coreControl.getEntityLongRanges(entityAttribute);
077        final var entityLongRangeDetails = new ArrayList<EntityLongRangeDetail>(entityLongRanges.size());
078        
079        entityLongRanges.forEach((entityLongRange) -> {
080            entityLongRangeDetails.add(entityLongRange.getLastDetail());
081        });
082        
083        return entityLongRangeDetails;
084    }
085
086    // Substantial portions of this are duplicated in getUserVisitSearchFacetObject(...).
087    public UserVisitSearchFacetTransfer getUserVisitSearchFacetTransfer(final ExecutionErrorAccumulator eea, final UserVisitSearch userVisitSearch,
088            final EntityAttribute entityAttribute) {
089        final var coreControl = Session.getModelController(CoreControl.class);
090        List<UserVisitSearchFacetListItemTransfer> userVisitSearchFacetListItemTransfers = null;
091        List<UserVisitSearchFacetIntegerTransfer> userVisitSearchFacetIntegerTransfers = null;
092        List<UserVisitSearchFacetLongTransfer> userVisitSearchFacetLongTransfers = null;
093        final var userVisit = userVisitSearch.getUserVisit();
094        final var entityAttributeTypeName = entityAttribute.getLastDetail().getEntityAttributeType().getEntityAttributeTypeName();
095
096        if(entityAttributeTypeName.equals(EntityAttributeTypes.LISTITEM.name())) {
097            final var entityListItemAttributeFacetResults = new EntityListItemAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
098
099            userVisitSearchFacetListItemTransfers = new ArrayList<>(entityListItemAttributeFacetResults.size());
100
101            for(var entityListItemAttributeFacetResult : entityListItemAttributeFacetResults) {
102                userVisitSearchFacetListItemTransfers.add(
103                        new UserVisitSearchFacetListItemTransfer(coreControl.getEntityListItemTransfer(userVisit, entityListItemAttributeFacetResult.getEntityListItem(), null),
104                                entityListItemAttributeFacetResult.getCount()));
105            }
106        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.MULTIPLELISTITEM.name())) {
107            final var entityMultipleListItemAttributeFacetResults = new EntityMultipleListItemAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
108
109            userVisitSearchFacetListItemTransfers = new ArrayList<>(entityMultipleListItemAttributeFacetResults.size());
110
111            for(var entityListItemAttributeFacetResult : entityMultipleListItemAttributeFacetResults) {
112                userVisitSearchFacetListItemTransfers.add(
113                        new UserVisitSearchFacetListItemTransfer(coreControl.getEntityListItemTransfer(userVisit, entityListItemAttributeFacetResult.getEntityListItem(), null),
114                                entityListItemAttributeFacetResult.getCount()));
115            }
116        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.INTEGER.name())) {
117            final var entityIntegerRangeDetails = getEntityIntegerRangeDetails(entityAttribute);
118            final var currentSize = entityIntegerRangeDetails.size();
119
120            userVisitSearchFacetIntegerTransfers = new ArrayList<>(currentSize); // maximum size, may be smaller.
121
122            if(currentSize > 0) {
123                final var entityIntegerAttributeFacetResults = new EntityIntegerAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
124
125                for(var entityIntegerAttributeFacetResult : entityIntegerAttributeFacetResults) {
126                    final var addedUserVisitSearchFacetIntegerTransfers = new ArrayList<UserVisitSearchFacetIntegerTransfer>();
127                    final var integerAttribute = entityIntegerAttributeFacetResult.getIntegerAttribute();
128
129                    for(Iterator<EntityIntegerRangeDetail> iter = entityIntegerRangeDetails.iterator(); iter.hasNext(); ) {
130                        final var entityIntegerRangeDetail = iter.next();
131                        final var minimumIntegerValue = entityIntegerRangeDetail.getMinimumIntegerValue();
132                        final var maximumIntegerValue = entityIntegerRangeDetail.getMaximumIntegerValue();
133
134                        if((minimumIntegerValue == null && maximumIntegerValue != null && integerAttribute <= maximumIntegerValue)
135                                || (minimumIntegerValue != null && maximumIntegerValue == null && integerAttribute >= minimumIntegerValue)
136                                || (minimumIntegerValue != null && maximumIntegerValue != null && integerAttribute >= minimumIntegerValue && integerAttribute <= maximumIntegerValue)) {
137                            addedUserVisitSearchFacetIntegerTransfers.add(
138                                    new UserVisitSearchFacetIntegerTransfer(coreControl.getEntityIntegerRangeTransfer(userVisit, entityIntegerRangeDetail.getEntityIntegerRange(), null),
139                                            entityIntegerAttributeFacetResult.getCount()));
140                            iter.remove();
141                        }
142                    }
143
144                    for(var userVisitSearchFacetIntegerTransfer : userVisitSearchFacetIntegerTransfers) {
145                        final var entityIntegerRange = userVisitSearchFacetIntegerTransfer.getEntityIntegerRange();
146                        final var minimumIntegerValue = entityIntegerRange.getMinimumIntegerValue();
147                        final var maximumIntegerValue = entityIntegerRange.getMaximumIntegerValue();
148
149                        if((minimumIntegerValue == null && maximumIntegerValue != null && integerAttribute <= maximumIntegerValue)
150                                || (minimumIntegerValue != null && maximumIntegerValue == null && integerAttribute >= minimumIntegerValue)
151                                || (minimumIntegerValue != null && maximumIntegerValue != null && integerAttribute >= minimumIntegerValue && integerAttribute <= maximumIntegerValue)) {
152                            userVisitSearchFacetIntegerTransfer.setCount(userVisitSearchFacetIntegerTransfer.getCount() + entityIntegerAttributeFacetResult.getCount());
153                        }
154                    }
155
156                    userVisitSearchFacetIntegerTransfers.addAll(addedUserVisitSearchFacetIntegerTransfers);
157                }
158            }
159
160            userVisitSearchFacetIntegerTransfers.sort(
161                    (UserVisitSearchFacetIntegerTransfer o1, UserVisitSearchFacetIntegerTransfer o2) -> Long.compare(o2.getCount(), o1.getCount())
162            );
163        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.LONG.name())) {
164            final var entityLongRangeDetails = getEntityLongRangeDetails(entityAttribute);
165            final var currentSize = entityLongRangeDetails.size();
166
167            userVisitSearchFacetLongTransfers = new ArrayList<>(currentSize); // maximum size, may be smaller.
168
169            if(currentSize > 0) {
170                final var entityLongAttributeFacetResults = new EntityLongAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
171
172                for(var entityLongAttributeFacetResult : entityLongAttributeFacetResults) {
173                    final var addedUserVisitSearchFacetLongTransfers = new ArrayList<UserVisitSearchFacetLongTransfer>();
174                    final var longAttribute = entityLongAttributeFacetResult.getLongAttribute();
175
176                    for(Iterator<EntityLongRangeDetail> iter = entityLongRangeDetails.iterator(); iter.hasNext(); ) {
177                        final var entityLongRangeDetail = iter.next();
178                        final var minimumLongValue = entityLongRangeDetail.getMinimumLongValue();
179                        final var maximumLongValue = entityLongRangeDetail.getMaximumLongValue();
180
181                        if((minimumLongValue == null && maximumLongValue != null && longAttribute <= maximumLongValue)
182                                || (minimumLongValue != null && maximumLongValue == null && longAttribute >= minimumLongValue)
183                                || (minimumLongValue != null && maximumLongValue != null && longAttribute >= minimumLongValue && longAttribute <= maximumLongValue)) {
184                            addedUserVisitSearchFacetLongTransfers.add(
185                                    new UserVisitSearchFacetLongTransfer(coreControl.getEntityLongRangeTransfer(userVisit, entityLongRangeDetail.getEntityLongRange(), null),
186                                            entityLongAttributeFacetResult.getCount()));
187                            iter.remove();
188                        }
189                    }
190
191                    for(var userVisitSearchFacetLongTransfer : userVisitSearchFacetLongTransfers) {
192                        final var entityLongRange = userVisitSearchFacetLongTransfer.getEntityLongRange();
193                        final var minimumLongValue = entityLongRange.getMinimumLongValue();
194                        final var maximumLongValue = entityLongRange.getMaximumLongValue();
195
196                        if((minimumLongValue == null && maximumLongValue != null && longAttribute <= maximumLongValue)
197                                || (minimumLongValue != null && maximumLongValue == null && longAttribute >= minimumLongValue)
198                                || (minimumLongValue != null && maximumLongValue != null && longAttribute >= minimumLongValue && longAttribute <= maximumLongValue)) {
199                            userVisitSearchFacetLongTransfer.setCount(userVisitSearchFacetLongTransfer.getCount() + entityLongAttributeFacetResult.getCount());
200                        }
201                    }
202
203                    userVisitSearchFacetLongTransfers.addAll(addedUserVisitSearchFacetLongTransfers);
204                }
205            }
206
207            userVisitSearchFacetLongTransfers.sort(
208                    (UserVisitSearchFacetLongTransfer o1, UserVisitSearchFacetLongTransfer o2) -> Long.compare(o2.getCount(), o1.getCount())
209            );
210        } else {
211            handleExecutionError(InvalidEntityAttributeTypeException.class, eea, ExecutionErrors.InvalidEntityAttributeType.name(), entityAttributeTypeName);
212        }
213
214        return eea.hasExecutionErrors() ? null : new UserVisitSearchFacetTransfer(coreControl.getEntityAttributeTransfer(userVisit, entityAttribute, null),
215                userVisitSearchFacetListItemTransfers == null ? null : new ListWrapper<>(userVisitSearchFacetListItemTransfers),
216                userVisitSearchFacetIntegerTransfers == null ? null : new ListWrapper<>(userVisitSearchFacetIntegerTransfers),
217                userVisitSearchFacetLongTransfers == null ? null : new ListWrapper<>(userVisitSearchFacetLongTransfers));
218    }
219
220    // Substantial portions of this are duplicated in getUserVisitSearchFacetTransfer(...).
221    public UserVisitSearchFacetObject getUserVisitSearchFacetObject(final ExecutionErrorAccumulator eea, final UserVisitSearch userVisitSearch,
222            final EntityAttribute entityAttribute) {
223        List<UserVisitSearchFacetListItemObject> userVisitSearchFacetListItemObjects = null;
224        List<UserVisitSearchFacetIntegerObject> userVisitSearchFacetIntegerObjects = null;
225        List<UserVisitSearchFacetLongObject> userVisitSearchFacetLongObjects = null;
226        final var entityAttributeTypeName = entityAttribute.getLastDetail().getEntityAttributeType().getEntityAttributeTypeName();
227
228        if(entityAttributeTypeName.equals(EntityAttributeTypes.LISTITEM.name())) {
229            final var entityListItemAttributeFacetResults = new EntityListItemAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
230
231            userVisitSearchFacetListItemObjects = new ArrayList<>(entityListItemAttributeFacetResults.size());
232
233            for(var entityListItemAttributeFacetResult : entityListItemAttributeFacetResults) {
234                userVisitSearchFacetListItemObjects.add(
235                        new UserVisitSearchFacetListItemObject(entityListItemAttributeFacetResult.getEntityListItem(),
236                                entityListItemAttributeFacetResult.getCount()));
237            }
238        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.MULTIPLELISTITEM.name())) {
239            final var entityMultipleListItemAttributeFacetResults = new EntityMultipleListItemAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
240
241            userVisitSearchFacetListItemObjects = new ArrayList<>(entityMultipleListItemAttributeFacetResults.size());
242
243            for(var entityListItemAttributeFacetResult : entityMultipleListItemAttributeFacetResults) {
244                userVisitSearchFacetListItemObjects.add(
245                        new UserVisitSearchFacetListItemObject(entityListItemAttributeFacetResult.getEntityListItem(),
246                                entityListItemAttributeFacetResult.getCount()));
247            }
248        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.INTEGER.name())) {
249            final var entityIntegerRangeDetails = getEntityIntegerRangeDetails(entityAttribute);
250            final var currentSize = entityIntegerRangeDetails.size();
251
252            userVisitSearchFacetIntegerObjects = new ArrayList<>(currentSize); // maximum size, may be smaller.
253
254            if(currentSize > 0) {
255                final var entityIntegerAttributeFacetResults = new EntityIntegerAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
256
257                for(var entityIntegerAttributeFacetResult : entityIntegerAttributeFacetResults) {
258                    final var addedUserVisitSearchFacetIntegerObjects = new ArrayList<UserVisitSearchFacetIntegerObject>();
259                    final var integerAttribute = entityIntegerAttributeFacetResult.getIntegerAttribute();
260
261                    for(Iterator<EntityIntegerRangeDetail> iter = entityIntegerRangeDetails.iterator(); iter.hasNext(); ) {
262                        final var entityIntegerRangeDetail = iter.next();
263                        final var minimumIntegerValue = entityIntegerRangeDetail.getMinimumIntegerValue();
264                        final var maximumIntegerValue = entityIntegerRangeDetail.getMaximumIntegerValue();
265
266                        if((minimumIntegerValue == null && maximumIntegerValue != null && integerAttribute <= maximumIntegerValue)
267                                || (minimumIntegerValue != null && maximumIntegerValue == null && integerAttribute >= minimumIntegerValue)
268                                || (minimumIntegerValue != null && maximumIntegerValue != null && integerAttribute >= minimumIntegerValue && integerAttribute <= maximumIntegerValue)) {
269                            addedUserVisitSearchFacetIntegerObjects.add(
270                                    new UserVisitSearchFacetIntegerObject(entityIntegerRangeDetail.getEntityIntegerRange(),
271                                            entityIntegerAttributeFacetResult.getCount()));
272                            iter.remove();
273                        }
274                    }
275
276                    for(var userVisitSearchFacetIntegerObject : userVisitSearchFacetIntegerObjects) {
277                        final var entityIntegerRange = userVisitSearchFacetIntegerObject.getEntityIntegerRange();
278                        final var minimumIntegerValue = entityIntegerRange.getLastDetail().getMinimumIntegerValue();
279                        final var maximumIntegerValue = entityIntegerRange.getLastDetail().getMaximumIntegerValue();
280
281                        if((minimumIntegerValue == null && maximumIntegerValue != null && integerAttribute <= maximumIntegerValue)
282                                || (minimumIntegerValue != null && maximumIntegerValue == null && integerAttribute >= minimumIntegerValue)
283                                || (minimumIntegerValue != null && maximumIntegerValue != null && integerAttribute >= minimumIntegerValue && integerAttribute <= maximumIntegerValue)) {
284                            userVisitSearchFacetIntegerObject.setCount(userVisitSearchFacetIntegerObject.getCount() + entityIntegerAttributeFacetResult.getCount());
285                        }
286                    }
287
288                    userVisitSearchFacetIntegerObjects.addAll(addedUserVisitSearchFacetIntegerObjects);
289                }
290            }
291
292            userVisitSearchFacetIntegerObjects.sort(
293                    (UserVisitSearchFacetIntegerObject o1, UserVisitSearchFacetIntegerObject o2) -> Long.compare(o2.getCount(), o1.getCount())
294            );
295        } else if(entityAttributeTypeName.equals(EntityAttributeTypes.LONG.name())) {
296            final var entityLongRangeDetails = getEntityLongRangeDetails(entityAttribute);
297            final var currentSize = entityLongRangeDetails.size();
298
299            userVisitSearchFacetLongObjects = new ArrayList<>(currentSize); // maximum size, may be smaller.
300
301            if(currentSize > 0) {
302                final var entityLongAttributeFacetResults = new EntityLongAttributeFacetQuery(userVisitSearch).execute(entityAttribute);
303
304                for(var entityLongAttributeFacetResult : entityLongAttributeFacetResults) {
305                    final var addedUserVisitSearchFacetLongObjects = new ArrayList<UserVisitSearchFacetLongObject>();
306                    final var longAttribute = entityLongAttributeFacetResult.getLongAttribute();
307
308                    for(Iterator<EntityLongRangeDetail> iter = entityLongRangeDetails.iterator(); iter.hasNext(); ) {
309                        final var entityLongRangeDetail = iter.next();
310                        final var minimumLongValue = entityLongRangeDetail.getMinimumLongValue();
311                        final var maximumLongValue = entityLongRangeDetail.getMaximumLongValue();
312
313                        if((minimumLongValue == null && maximumLongValue != null && longAttribute <= maximumLongValue)
314                                || (minimumLongValue != null && maximumLongValue == null && longAttribute >= minimumLongValue)
315                                || (minimumLongValue != null && maximumLongValue != null && longAttribute >= minimumLongValue && longAttribute <= maximumLongValue)) {
316                            addedUserVisitSearchFacetLongObjects.add(
317                                    new UserVisitSearchFacetLongObject(entityLongRangeDetail.getEntityLongRange(),
318                                            entityLongAttributeFacetResult.getCount()));
319                            iter.remove();
320                        }
321                    }
322
323                    for(var userVisitSearchFacetLongObject : userVisitSearchFacetLongObjects) {
324                        final var entityLongRange = userVisitSearchFacetLongObject.getEntityLongRange();
325                        final var minimumLongValue = entityLongRange.getLastDetail().getMinimumLongValue();
326                        final var maximumLongValue = entityLongRange.getLastDetail().getMaximumLongValue();
327
328                        if((minimumLongValue == null && maximumLongValue != null && longAttribute <= maximumLongValue)
329                                || (minimumLongValue != null && maximumLongValue == null && longAttribute >= minimumLongValue)
330                                || (minimumLongValue != null && maximumLongValue != null && longAttribute >= minimumLongValue && longAttribute <= maximumLongValue)) {
331                            userVisitSearchFacetLongObject.setCount(userVisitSearchFacetLongObject.getCount() + entityLongAttributeFacetResult.getCount());
332                        }
333                    }
334
335                    userVisitSearchFacetLongObjects.addAll(addedUserVisitSearchFacetLongObjects);
336                }
337            }
338
339            userVisitSearchFacetLongObjects.sort(
340                    (UserVisitSearchFacetLongObject o1, UserVisitSearchFacetLongObject o2) -> Long.compare(o2.getCount(), o1.getCount())
341            );
342        } else {
343            handleExecutionError(InvalidEntityAttributeTypeException.class, eea, ExecutionErrors.InvalidEntityAttributeType.name(), entityAttributeTypeName);
344        }
345
346        return (eea != null && eea.hasExecutionErrors()) ? null : new UserVisitSearchFacetObject(entityAttribute,
347                userVisitSearchFacetListItemObjects, userVisitSearchFacetIntegerObjects, userVisitSearchFacetLongObjects);
348    }
349
350}