• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opensrp / opensrp-client-core / #182

08 Aug 2024 12:55PM UTC coverage: 68.316% (-0.08%) from 68.395%
#182

Pull #925

github

web-flow
Merge 0883be0a0 into 4fcf06cf5
Pull Request #925: migrate SyncserviceJob to work manager

18308 of 26799 relevant lines covered (68.32%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.73
opensrp-core/src/main/java/org/smartregister/sync/ClientProcessorForJava.java
1
package org.smartregister.sync;
2

3
import static org.smartregister.event.Event.FORM_SUBMITTED;
4

5
import android.content.ContentValues;
6
import android.content.Context;
7

8
import androidx.annotation.NonNull;
9

10
import com.ibm.fhir.model.resource.QuestionnaireResponse;
11

12
import org.apache.commons.lang3.StringUtils;
13
import org.joda.time.DateTime;
14
import org.json.JSONArray;
15
import org.smartregister.AllConstants;
16
import org.smartregister.CoreLibrary;
17
import org.smartregister.commonregistry.AllCommonsRepository;
18
import org.smartregister.commonregistry.CommonRepository;
19
import org.smartregister.converters.ClientConverter;
20
import org.smartregister.converters.EventConverter;
21
import org.smartregister.domain.Address;
22
import org.smartregister.domain.Client;
23
import org.smartregister.domain.Event;
24
import org.smartregister.domain.Obs;
25
import org.smartregister.domain.PlanDefinition;
26
import org.smartregister.domain.db.EventClient;
27
import org.smartregister.domain.jsonmapping.ClassificationRule;
28
import org.smartregister.domain.jsonmapping.ClientClassification;
29
import org.smartregister.domain.jsonmapping.ClientField;
30
import org.smartregister.domain.jsonmapping.Column;
31
import org.smartregister.domain.jsonmapping.ColumnType;
32
import org.smartregister.domain.jsonmapping.JsonMapping;
33
import org.smartregister.domain.jsonmapping.Rule;
34
import org.smartregister.domain.jsonmapping.Table;
35
import org.smartregister.pathevaluator.plan.PlanEvaluator;
36
import org.smartregister.repository.DetailsRepository;
37
import org.smartregister.util.AppExecutors;
38
import org.smartregister.util.AssetHandler;
39

40
import java.lang.reflect.Field;
41
import java.text.SimpleDateFormat;
42
import java.util.ArrayList;
43
import java.util.Arrays;
44
import java.util.Collections;
45
import java.util.Date;
46
import java.util.HashMap;
47
import java.util.HashSet;
48
import java.util.List;
49
import java.util.Locale;
50
import java.util.Map;
51

52
import timber.log.Timber;
53

54
public class ClientProcessorForJava {
55

56
    public static final String JSON_ARRAY = "json_array";
57

58
    public static final String demo = "field";
59
    protected static final String VALUES_KEY = "values";
60
    protected static final String detailsUpdated = "detailsUpdated";
61
    protected static ClientProcessorForJava instance;
62
    protected HashMap<String, MiniClientProcessorForJava> processorMap = new HashMap<>();
1✔
63
    protected HashMap<MiniClientProcessorForJava, List<Event>> unsyncEventsPerProcessor = new HashMap<>();
1✔
64
    private String[] openmrsGenIds = {};
1✔
65
    private Map<String, Object> jsonMap = new HashMap<>();
1✔
66
    private Context mContext;
67

68
    private AppExecutors appExecutors;
69

70
    public ClientProcessorForJava(Context context) {
1✔
71
        mContext = context;
1✔
72
        appExecutors = new AppExecutors();
1✔
73
    }
1✔
74

75
    public static ClientProcessorForJava getInstance(Context context) {
76
        if (instance == null) {
1✔
77
            instance = new ClientProcessorForJava(context);
1✔
78
        }
79

80
        return instance;
1✔
81
    }
82

83

84
    public synchronized void processClient(List<EventClient> eventClientList) throws Exception {
85
        processClient(eventClientList, false);
1✔
86
    }
1✔
87

88
    public synchronized void processClient(List<EventClient> eventClientList, boolean localSubmission) throws Exception {
89

90
        final String EC_CLIENT_CLASSIFICATION = "ec_client_classification.json";
1✔
91
        ClientClassification clientClassification = assetJsonToJava(EC_CLIENT_CLASSIFICATION, ClientClassification.class);
1✔
92
        if (clientClassification == null) {
1✔
93
            return;
1✔
94
        }
95

96
        if (!eventClientList.isEmpty()) {
1✔
97
            for (EventClient eventClient : eventClientList) {
1✔
98
                // Iterate through the events
99
                Client client = eventClient.getClient();
1✔
100
                if (client != null) {
1✔
101
                    Event event = eventClient.getEvent();
1✔
102
                    String eventType = event.getEventType();
1✔
103

104
                    if (processorMap.containsKey(eventType)) {
1✔
105
                        try {
106
                            processEventUsingMiniProcessor(clientClassification, eventClient, eventType);
1✔
107
                        } catch (Exception ex) {
×
108
                            Timber.e(ex);
×
109
                        }
1✔
110
                    } else {
111
                        processEvent(event, client, clientClassification);
1✔
112
                    }
113
                }
114

115
                if (localSubmission && CoreLibrary.getInstance().getSyncConfiguration().runPlanEvaluationOnClientProcessing()) {
1✔
116
                    processPlanEvaluation(eventClient);
×
117
                }
118
            }
1✔
119
        }
120
    }
1✔
121

122
    /**
123
     * Process plan evaluation for an event client
124
     *
125
     * @param eventClient
126
     */
127
    public void processPlanEvaluation(EventClient eventClient) {
128
        appExecutors.diskIO().execute(() -> {
×
129
            String planIdentifier = eventClient.getEvent().getDetails().get("planIdentifier");
×
130

131
            if (StringUtils.isNotBlank(planIdentifier)) {
×
132
                PlanDefinition plan = CoreLibrary.getInstance().context().getPlanDefinitionRepository().findPlanDefinitionById(planIdentifier);
×
133
                PlanEvaluator planEvaluator = new PlanEvaluator(eventClient.getEvent().getProviderId());
×
134
                QuestionnaireResponse questionnaireResponse = EventConverter.convertEventToEncounterResource(eventClient.getEvent());
×
135
                if (eventClient.getClient() != null) {
×
136
                    questionnaireResponse = questionnaireResponse.toBuilder().contained(ClientConverter.convertClientToPatientResource(eventClient.getClient())).build();
×
137
                }
138
                planEvaluator.evaluatePlan(plan, questionnaireResponse);
×
139
            }
140
        });
×
141
    }
×
142

143
    /**
144
     * Call this method to flag the event as processed in the local repository.
145
     * All events valid or otherwise must be flagged to avoid re-processing
146
     *
147
     * @param event
148
     */
149
    public void completeProcessing(Event event) {
150
        if (event == null)
1✔
151
            return;
×
152

153
        if (event.getServerVersion() != 0) {
1✔
154
            CoreLibrary.getInstance().context().allSharedPreferences().updateLastClientProcessedTimeStamp(event.getServerVersion());
×
155
        }
156
        CoreLibrary.getInstance().context()
1✔
157
                .getEventClientRepository().markEventAsProcessed(event.getFormSubmissionId());
1✔
158
    }
1✔
159

160
    public Boolean processEvent(Event event, Client client, ClientClassification clientClassification) {
161
        try {
162
            // mark event as processed regardless of any errors
163
            completeProcessing(event);
1✔
164

165
            if (event.getCreator() != null) {
1✔
166
                Timber.i("EVENT from openmrs");
×
167
            }
168
            // For data integrity check if a client exists, if not pull one from cloudant and
169
            // insert in drishti sqlite db
170

171
            if (client == null) {
1✔
172
                return false;
1✔
173
            }
174

175
            // Get the client type classification
176
            List<ClassificationRule> clientClasses = clientClassification.case_classification_rules;
1✔
177
            if (clientClasses == null || clientClasses.isEmpty()) {
1✔
178
                return false;
1✔
179
            }
180

181
            // Check if child is deceased and skip
182
            if (client.getDeathdate() != null) {
1✔
183
                return false;
1✔
184
            }
185

186
            for (ClassificationRule clientClass : clientClasses) {
1✔
187
                processClientClass(clientClass, event, client);
1✔
188
            }
1✔
189

190
            // Incase the details have not been updated
191
            String updatedString = event.getDetails() != null ? event.getDetails().get(detailsUpdated) : null;
1✔
192
            if (StringUtils.isBlank(updatedString) || !Boolean.TRUE.toString().equals(updatedString)) {
1✔
193
                updateClientDetailsTable(event, client);
1✔
194
            }
195

196
            return true;
1✔
197
        } catch (Exception e) {
×
198
            Timber.e(e);
×
199
            return null;
×
200
        }
201
    }
202

203
    public Boolean processClientClass(ClassificationRule clientClass, Event event, Client client) {
204
        try {
205
            if (clientClass == null) {
1✔
206
                return false;
1✔
207
            }
208

209
            if (event == null) {
1✔
210
                return false;
1✔
211
            }
212

213
            if (client == null) {
1✔
214
                return false;
1✔
215
            }
216

217
            Rule rule = clientClass.rule;
1✔
218
            List<org.smartregister.domain.jsonmapping.Field> fields = rule.fields;
1✔
219

220
            for (org.smartregister.domain.jsonmapping.Field field : fields) {
1✔
221
                processField(field, event, client);
1✔
222
            }
1✔
223
            return true;
1✔
224
        } catch (Exception e) {
×
225
            Timber.e(e);
×
226
            return null;
×
227
        }
228
    }
229

230
    public Boolean processField(org.smartregister.domain.jsonmapping.Field field, Event event, Client client) {
231
        try {
232
            if (field == null) {
1✔
233
                return false;
1✔
234
            }
235

236
            // keep checking if the event data matches the values expected by each rule, break the
237
            // moment the rule fails
238
            String dataSegment = null;
1✔
239
            String fieldName = field.field;
1✔
240
            String fieldValue = field.field_value;
1✔
241
            String responseKey = null;
1✔
242

243
            if (fieldName != null && fieldName.contains(".")) {
1✔
244
                String fieldNameArray[] = fieldName.split("\\.");
1✔
245
                dataSegment = fieldNameArray[0];
1✔
246
                fieldName = fieldNameArray[1];
1✔
247
                String concept = field.concept;
1✔
248

249
                if (concept != null) {
1✔
250
                    fieldValue = concept;
1✔
251
                    responseKey = VALUES_KEY;
1✔
252
                }
253
            }
254

255
            List<String> createsCase = field.creates_case;
1✔
256
            List<String> closesCase = field.closes_case;
1✔
257

258
            // some fields are in the main doc e.g event_type so fetch them from the main doc
259
            if (StringUtils.isNotBlank(dataSegment)) {
1✔
260
                List<String> responseValues = field.values;
1✔
261
                Object dataSegmentObject = getValue(event, dataSegment);
1✔
262
                if (dataSegmentObject != null) {
1✔
263
                    if (dataSegmentObject instanceof List) {
1✔
264

265
                        List dataSegmentList = (List) dataSegmentObject;
1✔
266
                        // Iterate in the segment e.g obs segment
267
                        for (Object segment : dataSegmentList) {
1✔
268
                            // let's discuss this further, to get the real value in the doc we've to
269
                            // use the keys 'fieldcode' and 'value'
270
                            Object value = getValue(segment, fieldName);
1✔
271
                            String docSegmentFieldValue = value != null ? value.toString() : "";
1✔
272
                            Object values = getValue(segment, responseKey);
1✔
273
                            List<String> docSegmentResponseValues = new ArrayList<>();
1✔
274
                            if (values instanceof List) {
1✔
275
                                docSegmentResponseValues = getValues((List) values);
1✔
276
                            }
277

278
                            if (docSegmentFieldValue.equalsIgnoreCase(fieldValue) && (!Collections
1✔
279
                                    .disjoint(responseValues, docSegmentResponseValues))) {
1✔
280
                                // this is the event obs we're interested in put it in the respective
281
                                // bucket specified by type variable
282
                                processCaseModel(event, client, createsCase);
1✔
283
                                closeCase(client, closesCase);
1✔
284
                            }
285

286
                        }
1✔
287
                    } else if (dataSegmentObject instanceof Map) {
1✔
288
                        Map map = (Map) dataSegmentObject;
×
289
                        // This means field_value and response_key are null so pick the
290
                        // value from the json object for the field_name
291
                        if (map.containsKey(fieldName)) {
×
292
                            Object objectValue = map.get(fieldName);
×
293
                            if (objectValue != null && objectValue instanceof String) {
×
294
                                String docSegmentFieldValue = objectValue.toString();
×
295
                                if (docSegmentFieldValue.equalsIgnoreCase(fieldValue)) {
×
296
                                    processCaseModel(event, client, createsCase);
×
297
                                    closeCase(client, closesCase);
×
298
                                }
299
                            }
300
                        }
301
                    }
302
                }
303

304
            } else {
1✔
305
                //fetch from the main doc
306
                Object value = getValue(event, fieldName);
1✔
307
                String docSegmentFieldValue = value != null ? value.toString() : "";
1✔
308
                if (docSegmentFieldValue.equalsIgnoreCase(fieldValue)) {
1✔
309
                    processCaseModel(event, client, createsCase);
1✔
310
                    closeCase(client, closesCase);
1✔
311
                }
312
            }
313
            return true;
1✔
314
        } catch (Exception e) {
×
315
            Timber.e(e);
×
316
            return null;
×
317
        }
318
    }
319

320
    public Boolean closeCase(Client client, List<String> closesCase) {
321
        try {
322
            if (closesCase == null || closesCase.isEmpty()) {
1✔
323
                return false;
1✔
324
            }
325

326
            String baseEntityId = client.getBaseEntityId();
1✔
327
            String clientType = client.getClientType() != null ? client.getClientType() : (client.getRelationships() != null ? AllConstants.ECClientType.CHILD : null);
1✔
328

329
            for (String tableName : closesCase) {
1✔
330
                closeCase(tableName, baseEntityId);
1✔
331
                updateFTSsearch(tableName, clientType, baseEntityId, null);
1✔
332
            }
1✔
333

334
            return true;
1✔
335
        } catch (Exception e) {
×
336
            Timber.e(e);
×
337
            return null;
×
338
        }
339
    }
340

341
    public Boolean processCaseModel(Event event, Client client, List<String> createsCase) {
342
        try {
343

344
            if (createsCase == null || createsCase.isEmpty()) {
1✔
345
                return false;
1✔
346
            }
347
            for (String tableName : createsCase) {
1✔
348
                Table table = getColumnMappings(tableName);
1✔
349
                List<Column> columns = table.columns;
1✔
350
                String baseEntityId = getBaseEntityId(event, client, tableName);
1✔
351

352
                ContentValues contentValues = new ContentValues();
1✔
353
                //Add the base_entity_id
354
                contentValues.put(CommonRepository.BASE_ENTITY_ID_COLUMN, baseEntityId);
1✔
355
                contentValues.put(CommonRepository.IS_CLOSED_COLUMN, 0);
1✔
356

357
                for (Column colObject : columns) {
1✔
358
                    processCaseModel(event, client, colObject, contentValues);
1✔
359
                }
1✔
360

361
                // Modify openmrs generated identifier, Remove hyphen if it exists
362
                updateIdentifier(contentValues);
1✔
363

364
                // save the values to db
365
                executeInsertStatement(contentValues, tableName);
1✔
366

367
                String entityId = contentValues.getAsString(CommonRepository.BASE_ENTITY_ID_COLUMN);
1✔
368
                String clientType = client.getClientType() != null ? client.getClientType() : (client.getRelationships() != null ? AllConstants.ECClientType.CHILD : null);
1✔
369
                updateFTSsearch(tableName, clientType, entityId, contentValues);
1✔
370
                Long timestamp = getEventDate(event.getEventDate());
1✔
371
                addContentValuesToDetailsTable(contentValues, timestamp);
1✔
372
                updateClientDetailsTable(event, client);
1✔
373
            }
1✔
374

375
            return true;
1✔
376
        } catch (Exception e) {
1✔
377
            Timber.e(e);
1✔
378

379
            return null;
1✔
380
        }
381
    }
382

383
    /***
384
     * Method for retrieving baseEntityId used when processing Case Models
385
     * Allows customizing the baseEntityId for different cases
386
     * @param event event object
387
     * @param client client object
388
     * @param clientType client classification type
389
     * @return base entity id
390
     */
391
    protected String getBaseEntityId(Event event, Client client, String clientType) {
392
        return client != null ? client.getBaseEntityId() : event != null ? event.getBaseEntityId() : null;
1✔
393
    }
394

395
    public void processCaseModel(Event event, Client client, Column column, ContentValues contentValues) {
396
        try {
397
            String expectedEncounterType = event.getEventType();
1✔
398
            String docType = column.type;
1✔
399
            String columnName = column.column_name;
1✔
400
            JsonMapping jsonMapping = column.json_mapping;
1✔
401
            String dataSegment = null;
1✔
402
            String fieldName = jsonMapping.field;
1✔
403
            String fieldValue = null;
1✔
404
            String responseKey = null;
1✔
405

406
            String valueField = jsonMapping.value_field;
1✔
407

408
            if (fieldName != null && fieldName.contains(".")) {
1✔
409
                String fieldNameArray[] = fieldName.split("\\.");
1✔
410
                dataSegment = fieldNameArray[0];
1✔
411
                fieldName = fieldNameArray[1];
1✔
412
                fieldValue = StringUtils.isNotBlank(jsonMapping.concept) ? jsonMapping.concept
1✔
413
                        : (StringUtils.isNotBlank(jsonMapping.formSubmissionField) ? jsonMapping
1✔
414
                        .formSubmissionField : null);
1✔
415
                if (fieldValue != null) {
1✔
416
                    responseKey = VALUES_KEY;
1✔
417
                }
418
            }
419

420
            Object document = docType == null ? event : docType.equalsIgnoreCase("Event") ? event : client;
1✔
421

422
            Object docSegment;
423

424
            if (StringUtils.isNotBlank(dataSegment)) {
1✔
425
                // pick data from a specific section of the doc
426
                docSegment = getValue(document, dataSegment);
1✔
427
            } else {
428
                // else the use the main doc as the doc segment
429
                docSegment = document;
1✔
430
            }
431

432
            // special handler needed to process address,
433
            if (dataSegment != null && dataSegment.equalsIgnoreCase("addresses")) {
1✔
434
                Map<String, String> addressMap = getClientAddressAsMap(client);
1✔
435
                if (addressMap.containsKey(fieldName)) {
1✔
436
                    contentValues.put(columnName, addressMap.get(fieldName));
1✔
437
                }
438
                return;
1✔
439
            }
440

441
            // special handler for relationalid
442
            if (dataSegment != null && dataSegment.equalsIgnoreCase("relationships") && document instanceof Client) {
1✔
443
                Map<String, List<String>> relationshipMap = client.getRelationships();
1✔
444

445
                List<String> relationShipIds = relationshipMap.get(fieldName);
1✔
446
                if (relationShipIds != null && !relationShipIds.isEmpty()) {
1✔
447
                    contentValues.put(columnName, relationShipIds.get(0));
1✔
448
                }
449

450
                return;
1✔
451
            }
452

453
            String encounterType = jsonMapping.event_type;
1✔
454

455
            if (docSegment instanceof List) {
1✔
456

457
                List docSegmentList = (List) docSegment;
1✔
458

459
                for (Object segment : docSegmentList) {
1✔
460
                    String columnValue = null;
1✔
461

462
                    if (fieldValue == null) {
1✔
463
                        // This means field_value and response_key are null so pick the
464
                        // value from the json object for the field_name
465
                        columnValue = getValueAsString(segment, fieldName);
×
466
                    } else {
467
                        // this means field_value and response_key are not null e.g when
468
                        // retrieving some value in the events obs section
469
                        String expectedFieldValue = getValueAsString(segment, fieldName);
1✔
470
                        // some events can only be differentiated by the event_type value
471
                        // eg pnc1,pnc2, anc1,anc2
472
                        // check if encountertype (the one in ec_client_fields.json) is
473
                        // null or it matches the encounter type from the ec doc we're
474
                        // processing
475
                        boolean encounterTypeMatches =
1✔
476
                                (encounterType == null) || (encounterType
477
                                        .equalsIgnoreCase(expectedEncounterType));
1✔
478

479
                        if (encounterTypeMatches && expectedFieldValue
1✔
480
                                .equalsIgnoreCase(fieldValue)) {
1✔
481

482
                            if (StringUtils.isNotBlank(valueField)) {
1✔
483
                                columnValue = getValueAsString(segment, valueField);
1✔
484
                            }
485

486
                            if (columnValue == null) {
1✔
487
                                Object values = getValue(segment, responseKey);
×
488
                                if (values instanceof List) {
×
489
                                    columnValue = getValuesStr(segment, getValues((List) values), column.saveFormat);
×
490
                                }
491
                            }
492
                        }
493
                    }
494

495
                    // after successfully retrieving the column name and value store it
496
                    // in Content value
497
                    if (columnValue != null) {
1✔
498
                        columnValue = getHumanReadableConceptResponse(columnValue, segment);
1✔
499
                        String formattedValue = getFormattedValue(column, columnValue);
1✔
500
                        contentValues.put(columnName, formattedValue);
1✔
501
                    }
502
                }
1✔
503

504
            } else if (docSegment instanceof Map) {
1✔
505
                Map map = (Map) docSegment;
1✔
506
                // This means field_value and response_key are null so pick the
507
                // value from the json object for the field_name
508
                if (fieldValue == null && map.containsKey(fieldName)) {
1✔
509
                    Object mapValue = map.get(fieldName);
1✔
510
                    if (mapValue != null) {
1✔
511
                        if (mapValue instanceof String) {
1✔
512
                            String columnValue = getHumanReadableConceptResponse(mapValue.toString(), docSegment);
1✔
513
                            contentValues.put(columnName, columnValue);
1✔
514
                        } else {
1✔
515
                            contentValues.put(columnName, String.valueOf(mapValue));
×
516
                        }
517
                    }
518
                }
519
            } else {
1✔
520
                //e.g client attributes section
521
                String columnValue = getValueAsString(docSegment, fieldName);
1✔
522

523
                // after successfully retrieving the column name and value store it in
524
                // Content value
525
                if (columnValue != null) {
1✔
526
                    columnValue = getHumanReadableConceptResponse(columnValue,
1✔
527
                            docSegment);
528
                    contentValues.put(columnName, columnValue);
1✔
529
                }
530
            }
531
        } catch (Exception e) {
×
532
            Timber.e(e);
×
533
        }
1✔
534
    }
1✔
535

536
    /**
537
     * Formats values from {@param values} into a string based on {@param segment} properties
538
     *
539
     * @param segment
540
     * @param values
541
     * @return @return A formatted values String
542
     */
543
    private String getValuesStr(Object segment, List<String> values, String saveFormat) {
544
        String columnValue = null;
1✔
545
        if (values.isEmpty()) {
1✔
546
            return columnValue;
1✔
547
        }
548

549
        // save obs as json array string e.g ["val1","val2"] if specified by the developer
550
        if ((saveFormat != null && JSON_ARRAY.equals(saveFormat))
1✔
551
                || ((segment instanceof Obs) && ((Obs) segment).isSaveObsAsArray())) {
1✔
552
            columnValue = getValuesAsArray(values);
1✔
553
        } else {
554
            columnValue = values.get(0);
1✔
555
        }
556

557
        return columnValue;
1✔
558
    }
559

560
    private String getValuesAsArray(List<String> values) {
561
        JSONArray jsonArray = new JSONArray();
1✔
562
        for (String value : values) {
1✔
563
            jsonArray.put(value);
1✔
564
        }
1✔
565
        return jsonArray.toString();
1✔
566
    }
567

568
    /**
569
     * Reformat the data to be persisted in the database.
570
     * This function will reformat dates with supplied types for storage in the DB
571
     *
572
     * @param column
573
     * @param columnValue
574
     * @return
575
     */
576
    protected String getFormattedValue(Column column, String columnValue) {
577
        // covert the column if its a formatted column with both
578

579
        String dataType = StringUtils.isNotBlank(column.dataType) ? column.dataType : "";
1✔
580
        switch (dataType) {
1✔
581
            case ColumnType.Date:
582
                if (StringUtils.isNotBlank(column.saveFormat) && StringUtils.isNotBlank(column.sourceFormat)) {
1✔
583
                    try {
584
                        Date sourceDate = new SimpleDateFormat(column.sourceFormat, Locale.ENGLISH).parse(columnValue);
1✔
585
                        return new SimpleDateFormat(column.saveFormat, Locale.ENGLISH).format(sourceDate);
1✔
586
                    } catch (Exception e) {
×
587
                        Timber.e(e);
×
588
                    }
589
                }
590
            case ColumnType.String:
591
                if (StringUtils.isNotBlank(column.saveFormat)) {
1✔
592
                    return String.format(column.saveFormat, columnValue);
1✔
593
                }
594
                break;
595
            default:
596
                return columnValue;
1✔
597
        }
598

599
        return columnValue;
×
600
    }
601

602
    /**
603
     * Save the populated content values to details table
604
     *
605
     * @param values
606
     * @param eventDate
607
     */
608
    protected void addContentValuesToDetailsTable(ContentValues values, Long eventDate) {
609
        if (!CoreLibrary.getInstance().getSyncConfiguration().updateClientDetailsTable())
1✔
610
            return;
1✔
611

612
        try {
613
            String baseEntityId = values.getAsString("base_entity_id");
1✔
614

615
            for (String key : values.keySet()) {
1✔
616
                String value = values.getAsString(key);
1✔
617
                saveClientDetails(baseEntityId, key, value, eventDate);
1✔
618
            }
1✔
619
        } catch (Exception e) {
×
620
            Timber.e(e);
×
621
        }
1✔
622
    }
1✔
623

624
    /**
625
     * Update the details table with the new info, All the obs are extracted and saved as key
626
     * value with the key being the formSubmissionField and value being the value field.
627
     * If the key value already exists then the row will simply be updated with the value if the
628
     * event date is most recent
629
     *
630
     * @param event
631
     * @param client
632
     */
633
    public void updateClientDetailsTable(Event event, Client client) {
634
        try {
635
            Timber.d("Started updateClientDetailsTable");
1✔
636

637
            if (CoreLibrary.getInstance().getSyncConfiguration().updateClientDetailsTable()) {
1✔
638
                String baseEntityId = client.getBaseEntityId();
1✔
639
                Long timestamp = getEventDate(event.getEventDate());
1✔
640

641
                Map<String, String> genderInfo = getGender(client);
1✔
642
                saveClientDetails(baseEntityId, genderInfo, timestamp);
1✔
643

644
                Map<String, String> addressInfo = getClientAddressAsMap(client);
1✔
645
                saveClientDetails(baseEntityId, addressInfo, timestamp);
1✔
646

647
                Map<String, String> attributes = getClientAttributes(client);
1✔
648
                saveClientDetails(baseEntityId, attributes, timestamp);
1✔
649

650
                Map<String, String> obs = getObsFromEvent(event);
1✔
651
                saveClientDetails(baseEntityId, obs, timestamp);
1✔
652
            }
653

654
            event.addDetails(detailsUpdated, Boolean.TRUE.toString());
1✔
655

656
            Timber.d("Finished updateClientDetailsTable");
1✔
657
            // save the other misc, client info date of birth...
658
        } catch (Exception e) {
×
659
            Timber.e(e);
×
660
        }
1✔
661
    }
1✔
662

663
    /**
664
     * Retrieve the obs as key value pair <br/>
665
     * Key being the formSubmissionField and value being entered value
666
     *
667
     * @param event
668
     * @return
669
     */
670
    private Map<String, String> getObsFromEvent(Event event) {
671
        Map<String, String> obsMap = new HashMap<String, String>();
1✔
672

673
        try {
674
            List<Obs> obsList = event.getObs();
1✔
675
            for (Obs obs : obsList) {
1✔
676
                List<String> values = getValues(obs.getValues());
1✔
677
                String key = obs.getFormSubmissionField();
1✔
678
                if (StringUtils.isNotBlank(key)) {
1✔
679
                    for (String conceptValue : values) {
1✔
680
                        String value = getHumanReadableConceptResponse(conceptValue, obs);
1✔
681
                        if (StringUtils.isNotBlank(value)) {
1✔
682
                            obsMap.put(key, value);
1✔
683
                        }
684
                    }
1✔
685
                }
686
            }
1✔
687
        } catch (Exception e) {
×
688
            Timber.e(e);
×
689
        }
1✔
690
        return obsMap;
1✔
691
    }
692

693
    private Map<String, String> getClientAttributes(Client client) {
694
        Map<String, String> attributes = new HashMap<>();
1✔
695
        try {
696
            Map<String, Object> clientAttributes = client.getAttributes();
1✔
697
            for (Map.Entry<String, Object> entry : clientAttributes.entrySet()) {
1✔
698
                Object value = entry.getValue();
1✔
699
                String key = entry.getKey();
1✔
700

701
                if (value != null) {
1✔
702
                    attributes.put(key, value.toString());
1✔
703
                }
704
            }
1✔
705
        } catch (NullPointerException e) {
×
706
            Timber.e(e);
×
707
        }
1✔
708

709
        return attributes;
1✔
710
    }
711

712
    private Map<String, String> getGender(Client client) {
713
        Map<String, String> map = new HashMap<String, String>();
1✔
714
        final String GENDER = "gender";
1✔
715
        try {
716
            String gender = client.getGender();
1✔
717
            if (StringUtils.isNotBlank(gender)) {
1✔
718
                map.put(GENDER, gender);
1✔
719
            }
720
        } catch (NullPointerException e) {
×
721
            Timber.e(e);
×
722
        }
1✔
723

724
        return map;
1✔
725
    }
726

727
    public void saveClientDetails(String baseEntityId, Map<String, String> values, Long timestamp) {
728
        for (String key : values.keySet()) {
1✔
729
            String value = values.get(key);
1✔
730
            saveClientDetails(baseEntityId, key, value, timestamp);
1✔
731
        }
1✔
732
    }
1✔
733

734
    /**
735
     * Save a single details row to the db
736
     *
737
     * @param baseEntityId
738
     * @param key
739
     * @param value
740
     * @param timestamp
741
     */
742
    private void saveClientDetails(String baseEntityId, String key, String value, Long timestamp) {
743
        DetailsRepository detailsRepository = org.smartregister.CoreLibrary.getInstance().context().
1✔
744
                detailsRepository();
1✔
745
        detailsRepository.add(baseEntityId, key, value, timestamp);
1✔
746
    }
1✔
747

748

749
    /**
750
     * Get human readable values from the json doc humanreadablevalues key if the key is empty
751
     * return value
752
     *
753
     * @param value
754
     * @param object
755
     * @return
756
     * @throws Exception
757
     */
758
    protected String getHumanReadableConceptResponse(String value, Object object) {
759
        try {
760
            if (StringUtils.isBlank(value) || (object != null && !(object instanceof Obs))) {
1✔
761
                return value;
1✔
762
            }
763

764
            final String HUMAN_READABLE_VALUES = "humanReadableValues";
1✔
765
            List humanReadableValues = new ArrayList();
1✔
766
            Object humanReadableObject = getValue(object, HUMAN_READABLE_VALUES);
1✔
767
            if (humanReadableObject != null && humanReadableObject instanceof List) {
1✔
768
                humanReadableValues = (List) humanReadableObject;
1✔
769
            }
770

771
            if (object == null || humanReadableValues.isEmpty()) {
1✔
772
                String humanReadableValue = org.smartregister.CoreLibrary.getInstance().context().
1✔
773
                        customHumanReadableConceptResponse().get(value);
1✔
774

775
                if (StringUtils.isNotBlank(humanReadableValue)) {
1✔
776
                    return humanReadableValue;
×
777
                }
778

779
                return value;
1✔
780
            }
781

782
            return humanReadableValues.size() == 1 ? humanReadableValues.get(0).toString()
1✔
783
                    : humanReadableValues.toString();
×
784
        } catch (Exception e) {
×
785
            Timber.e(e);
×
786
        }
787
        return value;
×
788
    }
789

790
    public Map<String, String> getClientAddressAsMap(Client client) {
791
        Map<String, String> addressMap = new HashMap<String, String>();
1✔
792
        if (client == null) {
1✔
793
            return addressMap;
×
794
        }
795
        try {
796
            final String addressFieldsKey = "addressFields";
1✔
797

798
            List<Address> addressList = client.getAddresses();
1✔
799
            if (addressList != null && !addressList.isEmpty()) {
1✔
800
                Address address = addressList.get(0);
1✔
801
                Map<String, String> addressFieldMap = address.getAddressFields();
1✔
802
                if (addressFieldMap != null) {
1✔
803
                    for (Map.Entry<String, String> entry : addressFieldMap.entrySet()) {
1✔
804
                        addressMap.put(entry.getKey(), entry.getValue());
1✔
805
                    }
1✔
806
                }
807

808
                List<Field> fields = getFields(address.getClass());
1✔
809
                for (Field classField : fields) {
1✔
810
                    String fieldName = classField.getName();
1✔
811
                    if (!fieldName.equals(addressFieldsKey)) {
1✔
812
                        String value = getValueAsString(address, classField.getName());
1✔
813
                        if (value != null) {
1✔
814
                            addressMap.put(classField.getName(), value);
1✔
815
                        }
816
                    }
817
                }
1✔
818
            }
819
        } catch (Exception e) {
×
820
            Timber.e(e);
×
821
        }
1✔
822
        return addressMap;
1✔
823
    }
824

825
    /**
826
     * Insert the a new record to the database and returns its id
827
     **/
828
    public Long executeInsertStatement(ContentValues values, String tableName) {
829
        CommonRepository cr = org.smartregister.CoreLibrary.getInstance().context().commonrepository(tableName);
×
830
        return cr.executeInsertStatement(values, tableName);
×
831
    }
832

833
    public void closeCase(String tableName, String baseEntityId) {
834
        CommonRepository cr = org.smartregister.CoreLibrary.getInstance().context().commonrepository(tableName);
1✔
835
        cr.closeCase(baseEntityId, tableName);
1✔
836
    }
1✔
837

838
    public boolean deleteCase(String tableName, String baseEntityId) {
839
        CommonRepository cr = org.smartregister.CoreLibrary.getInstance().context().commonrepository(tableName);
×
840
        return cr.deleteCase(baseEntityId, tableName);
×
841
    }
842

843
    public Table getColumnMappings(String registerName) {
844
        try {
845
            ClientField clientField = assetJsonToJava(CoreLibrary.getInstance().getEcClientFieldsFile(), ClientField.class);
1✔
846
            if (clientField == null) {
1✔
847
                return null;
1✔
848
            }
849
            List<Table> bindObjects = clientField.bindobjects;
1✔
850
            for (Table bindObject : bindObjects) {
1✔
851
                if (bindObject.name.equalsIgnoreCase(registerName)) {
1✔
852
                    return bindObject;
1✔
853
                }
854
            }
×
855
        } catch (Exception e) {
×
856
            Timber.e(e);
×
857
        }
×
858
        return null;
×
859
    }
860

861
    protected <T> T assetJsonToJava(String fileName, Class<T> clazz) {
862
        return AssetHandler.assetJsonToJava(jsonMap, mContext, fileName, clazz);
1✔
863
    }
864

865
    protected List<String> getValues(List list) {
866
        List<String> values = new ArrayList<String>();
1✔
867
        if (list == null) {
1✔
868
            return values;
×
869
        }
870
        for (Object o : list) {
1✔
871
            if (o != null) {
1✔
872
                values.add(o.toString());
1✔
873
            }
874
        }
1✔
875
        return values;
1✔
876
    }
877

878
    protected Object getValue(Object instance, String fieldName) {
879
        if (instance == null || StringUtils.isBlank(fieldName)) {
1✔
880
            return null;
×
881
        }
882
        try {
883
            Field field = getField(instance.getClass(), fieldName);
1✔
884
            if (field == null) {
1✔
885
                return null;
×
886
            }
887
            field.setAccessible(true);
1✔
888
            return field.get(instance);
1✔
889
        } catch (IllegalAccessException e) {
×
890
            return null;
×
891
        }
892
    }
893

894
    protected String getValueAsString(Object instance, String fieldName) {
895
        Object object = getValue(instance, fieldName);
1✔
896
        if (object != null) {
1✔
897
            return object.toString();
1✔
898
        }
899
        return null;
1✔
900
    }
901

902

903
    private long getEventDate(DateTime eventDate) {
904
        if (eventDate == null) {
1✔
905
            return new Date().getTime();
×
906
        } else {
907
            return eventDate.getMillis();
1✔
908
        }
909
    }
910

911
    private List<Field> getFields(Class clazz) {
912
        List<Field> fields = new ArrayList<>();
1✔
913
        if (instance == null) {
1✔
914
            return new ArrayList<>();
×
915
        }
916

917

918
        Class current = clazz;
1✔
919
        while (current != null) { // we don't want to process Object.class
1✔
920
            // do something with current's fields
921
            Field[] fieldArray = current.getDeclaredFields();
1✔
922
            if (fieldArray != null) {
1✔
923
                fields.addAll(Arrays.asList(fieldArray));
1✔
924
            }
925

926
            current = current.getSuperclass();
1✔
927
        }
1✔
928

929
        return fields;
1✔
930
    }
931

932
    private Field getField(Class clazz, String fieldName) {
933
        if (clazz == null || StringUtils.isBlank(fieldName)) {
1✔
934
            return null;
×
935
        }
936

937
        Field field = null;
1✔
938
        try {
939
            field = clazz.getDeclaredField(fieldName);
1✔
940
        } catch (NoSuchFieldException e) {
1✔
941
            // No need to log this, log will be to big
942
        }
1✔
943
        if (field != null) {
1✔
944
            return field;
1✔
945
        }
946

947
        return getField(clazz.getSuperclass(), fieldName);
1✔
948
    }
949

950
    /**
951
     * Update the fts table with the provided values. This overloaded method adds the parameter entityType to uniquely distiguish the client types being processed
952
     *
953
     * @param tableName     the case table
954
     * @param entityType    the client type or bind type
955
     * @param entityId      the entity identifier
956
     * @param contentValues the fields to update and corresponding values
957
     */
958
    public void updateFTSsearch(String tableName, String entityType, String entityId, ContentValues contentValues) {
959
        updateFTSsearch(tableName, entityId, contentValues);
1✔
960
    }
1✔
961

962
    public void updateFTSsearch(String tableName, String entityId, ContentValues contentValues) {
963
        Timber.d("Starting updateFTSsearch table: " + tableName);
1✔
964
        AllCommonsRepository allCommonsRepository = org.smartregister.CoreLibrary.getInstance().context().
1✔
965
                allCommonsRepositoryobjects(tableName);
1✔
966

967
        if (allCommonsRepository != null) {
1✔
968
            allCommonsRepository.updateSearch(entityId);
×
969
            updateRegisterCount(entityId);
×
970
        }
971

972
        Timber.d("Finished updateFTSsearch table: " + tableName);
1✔
973
    }
1✔
974

975
    protected void updateRegisterCount(String entityId) {
976
        FORM_SUBMITTED.notifyListeners(entityId);
×
977
    }
×
978

979
    /**
980
     * Update given OPENMRS identifier, removes hyphen
981
     *
982
     * @param values
983
     */
984
    private void updateIdentifier(ContentValues values) {
985
        try {
986
            for (String identifier : getOpenmrsGenIds()) {
1✔
987
                Object value = values.get(identifier); //TODO
1✔
988
                if (value != null) {
1✔
989
                    String sValue = value.toString();
1✔
990
                    if (value instanceof String && StringUtils.isNotBlank(sValue)) {
1✔
991
                        values.remove(identifier);
1✔
992
                        values.put(identifier, sValue.replace("-", ""));
1✔
993
                    }
994
                }
995
            }
996
        } catch (Exception e) {
×
997
            Timber.e(e);
×
998
        }
1✔
999
    }
1✔
1000

1001
    public Context getContext() {
1002
        return mContext;
×
1003
    }
1004

1005
    protected String[] getOpenmrsGenIds() {
1006
        return openmrsGenIds;
1✔
1007
    }
1008

1009
    protected void addMiniProcessors(MiniClientProcessorForJava... miniClientProcessorsForJava) {
1010
        for (MiniClientProcessorForJava miniClientProcessorForJava : miniClientProcessorsForJava) {
1✔
1011
            unsyncEventsPerProcessor.put(miniClientProcessorForJava, new ArrayList<Event>());
1✔
1012

1013
            HashSet<String> eventTypes = miniClientProcessorForJava.getEventTypes();
1✔
1014

1015
            for (String eventType : eventTypes) {
1✔
1016
                processorMap.put(eventType, miniClientProcessorForJava);
1✔
1017
            }
1✔
1018
        }
1019
    }
1✔
1020

1021
    protected void processEventUsingMiniProcessor(@NonNull ClientClassification clientClassification, @NonNull EventClient eventClient, @NonNull String eventType) throws Exception {
1022
        MiniClientProcessorForJava miniClientProcessorForJava = processorMap.get(eventType);
1✔
1023
        if (miniClientProcessorForJava != null) {
1✔
1024
            List<Event> processorUnsyncEvents = unsyncEventsPerProcessor.get(miniClientProcessorForJava);
1✔
1025
            if (processorUnsyncEvents == null) {
1✔
1026
                processorUnsyncEvents = new ArrayList<>();
×
1027
                unsyncEventsPerProcessor.put(miniClientProcessorForJava, processorUnsyncEvents);
×
1028
            }
1029

1030
            completeProcessing(eventClient.getEvent());
1✔
1031
            miniClientProcessorForJava.processEventClient(eventClient, processorUnsyncEvents, clientClassification);
1✔
1032
        }
1033
    }
1✔
1034
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc