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.model.control.contact.server.indexer;
018
019import com.echothree.model.control.contact.common.ContactMechanismTypes;
020import com.echothree.model.control.contact.server.analyzer.ContactMechanismAnalyzer;
021import com.echothree.model.control.contact.server.control.ContactControl;
022import com.echothree.model.control.geo.server.control.GeoControl;
023import com.echothree.model.control.index.common.IndexConstants;
024import com.echothree.model.control.index.common.IndexFieldVariations;
025import com.echothree.model.control.index.common.IndexFields;
026import com.echothree.model.control.index.server.indexer.BaseIndexer;
027import com.echothree.model.control.index.server.indexer.FieldTypes;
028import com.echothree.model.data.contact.server.entity.ContactMechanism;
029import com.echothree.model.data.contact.server.entity.ContactMechanismPurpose;
030import com.echothree.model.data.core.server.entity.EntityInstance;
031import com.echothree.model.data.geo.server.entity.GeoCode;
032import com.echothree.model.data.index.server.entity.Index;
033import com.echothree.model.data.party.server.entity.Language;
034import com.echothree.model.data.party.server.entity.Party;
035import com.echothree.model.data.party.server.entity.PartyType;
036import com.echothree.util.common.string.Inet4AddressUtils;
037import com.echothree.util.server.message.ExecutionErrorAccumulator;
038import java.util.HashSet;
039import java.util.Set;
040import javax.enterprise.context.Dependent;
041import javax.inject.Inject;
042import org.apache.lucene.analysis.Analyzer;
043import org.apache.lucene.document.Document;
044import org.apache.lucene.document.Field;
045import org.apache.lucene.document.SortedDocValuesField;
046import org.apache.lucene.util.BytesRef;
047
048@Dependent
049public class ContactMechanismIndexer
050        extends BaseIndexer<ContactMechanism> {
051
052    @Inject
053    ContactControl contactControl;
054
055    @Inject
056    GeoControl geoControl;
057
058    @Override
059    public BaseIndexer<ContactMechanism> setup(final ExecutionErrorAccumulator eea, final Index index) {
060        return super.setup(eea, index);
061    }
062
063    @Override
064    protected Analyzer getAnalyzer() {
065        return new ContactMechanismAnalyzer(eea, language, entityType, entityAliasTypes, entityAttributes, tagScopes);
066    }
067    
068    @Override
069    protected ContactMechanism getEntity(final EntityInstance entityInstance) {
070        return contactControl.getContactMechanismByEntityInstance(entityInstance);
071    }
072    
073    private void addPartyNamesToDocument(Document document, Set<Party> parties) {
074        var partyNamesBuilder = new StringBuilder();
075
076        parties.forEach((party) -> {
077            if(partyNamesBuilder.length() != 0) {
078                partyNamesBuilder.append(' ');
079            }
080
081            partyNamesBuilder.append(party.getLastDetail().getPartyName());
082        });
083
084        document.add(new Field(IndexFields.partyNames.name(), partyNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
085    }
086    
087    private void addPartyTypeNamesToDocument(Document document, Set<PartyType> partyTypes) {
088        var partyTypeNamesBuilder = new StringBuilder();
089
090        partyTypes.forEach((partyType) -> {
091            if(partyTypeNamesBuilder.length() != 0) {
092                partyTypeNamesBuilder.append(' ');
093            }
094
095            partyTypeNamesBuilder.append(partyType.getPartyTypeName());
096        });
097
098        document.add(new Field(IndexFields.partyTypeNames.name(), partyTypeNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
099    }
100    
101    private void addContactMechanismPurposeNamesToDocument(Document document, Set<ContactMechanismPurpose> contactMechanismPurposes) {
102        var contactMechanismPurposesNamesBuilder = new StringBuilder();
103
104        contactMechanismPurposes.forEach((contactMechanismPurpose) -> {
105            if(contactMechanismPurposesNamesBuilder.length() != 0) {
106                contactMechanismPurposesNamesBuilder.append(' ');
107            }
108
109            contactMechanismPurposesNamesBuilder.append(contactMechanismPurpose.getContactMechanismPurposeName());
110        });
111
112        document.add(new Field(IndexFields.contactMechanismPurposeNames.name(), contactMechanismPurposesNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
113    }
114    
115    private void addPartiesToDocument(final Document document, final ContactMechanism contactMechanism) {
116        Set<Party> parties = new HashSet<>();
117        Set<PartyType> partyTypes = new HashSet<>();
118        Set<ContactMechanismPurpose> contactMechanismPurposes = new HashSet<>();
119        
120        contactControl.getPartyContactMechanismsByContactMechanism(contactMechanism).forEach((partyContactMechanism) -> {
121            var partyContactMechanismDetail = partyContactMechanism.getLastDetail();
122            var party = partyContactMechanismDetail.getParty();
123            parties.add(party);
124            partyTypes.add(party.getLastDetail().getPartyType());
125            contactControl.getPartyContactMechanismPurposesByPartyContactMechanism(partyContactMechanism).forEach((partyContactMechanismPurpose) -> {
126                contactMechanismPurposes.add(partyContactMechanismPurpose.getLastDetail().getContactMechanismPurpose());
127            });
128        });
129        
130        addPartyNamesToDocument(document, parties);
131        addPartyTypeNamesToDocument(document, partyTypes);
132        addContactMechanismPurposeNamesToDocument(document, contactMechanismPurposes);
133    }
134    
135    private void addGeoCodeToDocument(final Document document, final Language language, final GeoCode geoCode, String description, final String geoCodeIndexField, final String descriptionIndexField) {
136        if(geoCode != null) {
137            description = geoControl.getBestGeoCodeDescription(geoCode, language);
138
139            document.add(new Field(geoCodeIndexField, geoCode.getLastDetail().getGeoCodeName(), FieldTypes.NOT_STORED_TOKENIZED));
140        }
141
142        if(description != null) {
143            document.add(new Field(descriptionIndexField, description, FieldTypes.NOT_STORED_TOKENIZED));
144        }
145    }
146    
147    private void addContactEmailAddressToDocument(final Document document, final ContactMechanism contactMechanism) {
148        var contactEmailAddress = contactControl.getContactEmailAddress(contactMechanism);
149
150        if(contactEmailAddress != null) {
151            document.add(new Field(IndexFields.emailAddress.name(), contactEmailAddress.getEmailAddress(), FieldTypes.NOT_STORED_TOKENIZED));
152        }
153    }
154
155    private void addContactInet4AddressToDocument(final Document document, final ContactMechanism contactMechanism) {
156        var contactInet4Address = contactControl.getContactInet4Address(contactMechanism);
157
158        if(contactInet4Address != null) {
159            document.add(new Field(IndexFields.inet4Address.name(), Inet4AddressUtils.getInstance().formatInet4Address(contactInet4Address.getInet4Address()), FieldTypes.NOT_STORED_TOKENIZED));
160        }
161    }
162
163    private void addContactPostalAddressToDocument(final Document document, final ContactMechanism contactMechanism, final Language language) {
164        var contactPostalAddress = contactControl.getContactPostalAddress(contactMechanism);
165
166        if(contactPostalAddress != null) {
167            var personalTitle = contactPostalAddress.getPersonalTitle();
168            var firstName = contactPostalAddress.getFirstName();
169            var middleName = contactPostalAddress.getMiddleName();
170            var lastName = contactPostalAddress.getLastName();
171            var nameSuffix = contactPostalAddress.getNameSuffix();
172            var companyName = contactPostalAddress.getCompanyName();
173            var attention = contactPostalAddress.getAttention();
174            var address2 = contactPostalAddress.getAddress2();
175            var address3 = contactPostalAddress.getAddress3();
176
177            if(personalTitle != null) {
178                document.add(new Field(IndexFields.personalTitle.name(), personalTitle.getLastDetail().getDescription(), FieldTypes.NOT_STORED_TOKENIZED));
179            }
180
181            if(firstName != null) {
182                document.add(new Field(IndexFields.firstName.name(), firstName, FieldTypes.NOT_STORED_TOKENIZED));
183            }
184
185            if(middleName != null) {
186                document.add(new Field(IndexFields.middleName.name(), middleName, FieldTypes.NOT_STORED_TOKENIZED));
187            }
188
189            if(lastName != null) {
190                document.add(new Field(IndexFields.lastName.name(), lastName, FieldTypes.NOT_STORED_TOKENIZED));
191            }
192
193            if(nameSuffix != null) {
194                document.add(new Field(IndexFields.nameSuffix.name(), nameSuffix.getLastDetail().getDescription(), FieldTypes.NOT_STORED_TOKENIZED));
195            }
196
197            if(companyName != null) {
198                document.add(new Field(IndexFields.companyName.name(), companyName, FieldTypes.NOT_STORED_TOKENIZED));
199            }
200
201            if(attention != null) {
202                document.add(new Field(IndexFields.attention.name(), attention, FieldTypes.NOT_STORED_TOKENIZED));
203            }
204
205            document.add(new Field(IndexFields.address1.name(), contactPostalAddress.getAddress1(), FieldTypes.NOT_STORED_TOKENIZED));
206
207            if(address2 != null) {
208                document.add(new Field(IndexFields.address2.name(), address2, FieldTypes.NOT_STORED_TOKENIZED));
209            }
210
211            if(address3 != null) {
212                document.add(new Field(IndexFields.address3.name(), address3, FieldTypes.NOT_STORED_TOKENIZED));
213            }
214
215            addGeoCodeToDocument(document, language, contactPostalAddress.getCityGeoCode(), contactPostalAddress.getCity(), IndexFields.cityGeoCodeName.name(), IndexFields.city.name());
216            addGeoCodeToDocument(document, language, contactPostalAddress.getCountyGeoCode(), null, IndexFields.countyGeoCodeName.name(), IndexFields.county.name());
217            addGeoCodeToDocument(document, language, contactPostalAddress.getStateGeoCode(), contactPostalAddress.getState(), IndexFields.stateGeoCodeName.name(), IndexFields.state.name());
218            addGeoCodeToDocument(document, language, contactPostalAddress.getPostalCodeGeoCode(), contactPostalAddress.getPostalCode(), IndexFields.postalCodeGeoCodeName.name(), IndexFields.postalCode.name());
219            addGeoCodeToDocument(document, language, contactPostalAddress.getCountryGeoCode(), null, IndexFields.countryGeoCodeName.name(), IndexFields.country.name());
220
221            document.add(new Field(IndexFields.isCommercial.name(), contactPostalAddress.getIsCommercial().toString(), FieldTypes.NOT_STORED_TOKENIZED));
222        }
223    }
224
225    private void addContactTelephoneToDocument(final Document document, final ContactMechanism contactMechanism) {
226        var contactTelephone = contactControl.getContactTelephone(contactMechanism);
227
228        if(contactTelephone != null) {
229            var areaCode = contactTelephone.getAreaCode();
230            var telephoneExtension = contactTelephone.getTelephoneExtension();
231
232            document.add(new Field(IndexFields.countryGeoCodeName.name(), contactTelephone.getCountryGeoCode().getLastDetail().getGeoCodeName(), FieldTypes.NOT_STORED_TOKENIZED));
233
234            if(areaCode != null) {
235                document.add(new Field(IndexFields.areaCode.name(), areaCode, FieldTypes.NOT_STORED_TOKENIZED));
236            }
237
238            document.add(new Field(IndexFields.telephoneNumber.name(), contactTelephone.getTelephoneNumber(), FieldTypes.NOT_STORED_TOKENIZED));
239
240            if(telephoneExtension != null) {
241                document.add(new Field(IndexFields.telephoneExtension.name(), telephoneExtension, FieldTypes.NOT_STORED_TOKENIZED));
242            }
243        }
244    }
245
246    private void addContactWebAddressToDocument(final Document document, final ContactMechanism contactMechanism) {
247        var contactWebAddress = contactControl.getContactWebAddress(contactMechanism);
248
249        if(contactWebAddress != null) {
250            document.add(new Field(IndexFields.url.name(), contactWebAddress.getUrl(), FieldTypes.NOT_STORED_TOKENIZED));
251        }
252    }
253    
254    private void addContactMechanismToDocument(final Document document, final ContactMechanism contactMechanism, final Language language) {
255        var contactMechanismDetail = contactMechanism.getLastDetail();
256        var contactMechanismTypeName = contactMechanismDetail.getContactMechanismType().getContactMechanismTypeName();
257        
258        document.add(new Field(IndexFields.contactMechanismName.name(), contactMechanismDetail.getContactMechanismName(), FieldTypes.NOT_STORED_TOKENIZED));
259        document.add(new SortedDocValuesField(IndexFields.contactMechanismName.name() + IndexConstants.INDEX_FIELD_VARIATION_SEPARATOR + IndexFieldVariations.sortable.name(),
260                new BytesRef(contactMechanismDetail.getContactMechanismName())));
261
262        document.add(new Field(IndexFields.contactMechanismTypeName.name(), contactMechanismTypeName, FieldTypes.NOT_STORED_TOKENIZED));
263        document.add(new Field(IndexFields.allowSolicitation.name(), contactMechanismDetail.getAllowSolicitation().toString(), FieldTypes.NOT_STORED_TOKENIZED));
264        
265        if(contactMechanismTypeName.equals(ContactMechanismTypes.EMAIL_ADDRESS.name())) {
266            addContactEmailAddressToDocument(document, contactMechanism);
267        } else if(contactMechanismTypeName.equals(ContactMechanismTypes.INET_4.name())) {
268            addContactInet4AddressToDocument(document, contactMechanism);
269        } else if(contactMechanismTypeName.equals(ContactMechanismTypes.INET_6.name())) {
270            // TODO
271        } else if(contactMechanismTypeName.equals(ContactMechanismTypes.POSTAL_ADDRESS.name())) {
272            addContactPostalAddressToDocument(document, contactMechanism, language);
273        } else if(contactMechanismTypeName.equals(ContactMechanismTypes.TELECOM_ADDRESS.name())) {
274            addContactTelephoneToDocument(document, contactMechanism);
275        } else if(contactMechanismTypeName.equals(ContactMechanismTypes.WEB_ADDRESS.name())) {
276            addContactWebAddressToDocument(document, contactMechanism);
277        }
278    }
279    
280    @Override
281    protected Document convertToDocument(final EntityInstance entityInstance, final ContactMechanism contactMechanism) {
282        var document = newDocumentWithEntityInstanceFields(entityInstance, contactMechanism.getPrimaryKey());
283
284        addPartiesToDocument(document, contactMechanism);
285        addContactMechanismToDocument(document, contactMechanism, language);
286
287        return document;
288    }
289
290}