Project

General

Profile

Download (23.6 KB) Statistics
| Branch: | Tag: | Revision:
1 3dd6c00e Andreas Müller
/**
2
 * Copyright (C) 2015 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9
package eu.etaxonomy.cdm.persistence.dao.jdbc.validation;
10
11
import java.sql.Connection;
12
import java.sql.Date;
13
import java.sql.PreparedStatement;
14
import java.sql.ResultSet;
15
import java.sql.SQLException;
16
import java.sql.Types;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.Set;
20
import java.util.UUID;
21
22
import javax.sql.DataSource;
23
import javax.validation.ConstraintViolation;
24
25 23de68fc Andreas Müller
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
26 3dd6c00e Andreas Müller
import org.joda.time.DateTime;
27
import org.springframework.beans.factory.annotation.Autowired;
28
import org.springframework.stereotype.Repository;
29
30
import eu.etaxonomy.cdm.model.common.ICdmBase;
31
import eu.etaxonomy.cdm.model.validation.CRUDEventType;
32
import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;
33
import eu.etaxonomy.cdm.model.validation.EntityValidation;
34
import eu.etaxonomy.cdm.model.validation.EntityValidationStatus;
35
import eu.etaxonomy.cdm.model.validation.Severity;
36
import eu.etaxonomy.cdm.persistence.dao.jdbc.JdbcDaoUtils;
37
import eu.etaxonomy.cdm.persistence.dao.validation.IEntityValidationCrud;
38
39
/**
40
 * @author ayco_holleman
41 53db84af Andreas Müller
 * @since 16 jan. 2015
42 3dd6c00e Andreas Müller
 */
43
@Repository
44
public class EntityValidationCrudJdbcImpl implements IEntityValidationCrud {
45
46 23de68fc Andreas Müller
    public static final Logger logger = LogManager.getLogger(EntityValidationCrudJdbcImpl.class);
47 3dd6c00e Andreas Müller
48
    private static final String SQL_INSERT_VALIDATION_RESULT = "INSERT INTO entityvalidation"
49
            + "(id, created, uuid,  crudeventtype, validatedentityclass, validatedentityid,"
50
            + "validatedentityuuid, userfriendlydescription, userfriendlytypename, validationcount,"
51
            + "updated, status, createdby_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";
52
53
    private static final int vr_id = 1;
54
    private static final int vr_created = 2;
55
    private static final int vr_uuid = 3;
56
    private static final int vr_crudeventtype = 4;
57
    private static final int vr_validatedentityclass = 5;
58
    private static final int vr_validatedentityid = 6;
59
    private static final int vr_validatedentityuuid = 7;
60
    private static final int vr_userfriendlydescription = 8;
61
    private static final int vr_userfriendlytypename = 9;
62
    private static final int vr_validationcount = 10;
63
    private static final int vr_updated = 11;
64
    private static final int vr_status = 12;
65
    private static final int vr_createdby_id = 13;
66
67
    private static final String SQL_INSERT_CONSTRAINT_VIOLATION = "INSERT INTO entityconstraintviolation"
68
            + "(id, created, uuid,  invalidvalue, message, propertypath, userfriendlyfieldname, severity,"
69
            + "validator, validationgroup, createdby_id, entityvalidation_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
70
71
    private static final int cv_id = 1;
72
    private static final int cv_created = 2;
73
    private static final int cv_uuid = 3;
74
    private static final int cv_invalidvalue = 4;
75
    private static final int cv_message = 5;
76
    private static final int cv_propertypath = 6;
77
    private static final int cv_userfriendlyfieldname = 7;
78
    private static final int cv_severity = 8;
79
    private static final int cv_validator = 9;
80
    private static final int cv_validationgroup = 10;
81
    private static final int cv_createdby_id = 11;
82
    private static final int cv_entityvalidation_id = 12;
83
84
    @Autowired
85
    private DataSource datasource;
86
87
    public EntityValidationCrudJdbcImpl() {
88
89
    }
90
91
    public EntityValidationCrudJdbcImpl(DataSource datasource) {
92
        this.datasource = datasource;
93
    }
94
95
    public void setDatasource(DataSource datasource) {
96
        this.datasource = datasource;
97
    }
98
99
    @Override
100
    public <T extends ICdmBase> void saveEntityValidation(T validatedEntity, Set<ConstraintViolation<T>> errors,
101
            CRUDEventType crudEventType, Class<?>[] validationGroups) {
102
        saveEntityValidation(createEntityValidation(validatedEntity, errors, crudEventType), validationGroups);
103
    }
104
105
    // This is the method that's tested by the unit tests
106
    // rather than the interface method above, because it
107
    // is almost impossible to create a mock instance of
108
    // ConstraintViolation<T>
109
    void saveEntityValidation(EntityValidation newValidation, Class<?>[] validationGroups) {
110
        Connection conn = null;
111
        EntityValidation tmp = null;
112
        try {
113
            conn = datasource.getConnection();
114
            JdbcDaoUtils.startTransaction(conn);
115
            String entityClass = newValidation.getValidatedEntityClass();
116
            int entityId = newValidation.getValidatedEntityId();
117
            EntityValidation oldValidation = getEntityValidation(conn, entityClass, entityId);
118
            if (oldValidation == null) {
119
                tmp = newValidation;
120
                /*
121
                 * The entity has never been validated before. We should now
122
                 * create an entityvalidation record whether or not the entity
123
                 * has errors, because the entity HAS been validated so its
124
                 * validationcount is now 1.
125
                 */
126
                saveEntityValidationRecord(conn, newValidation);
127
                Set<EntityConstraintViolation> errors = newValidation.getEntityConstraintViolations();
128
                if (errors != null && errors.size() != 0) {
129
                    saveErrorRecords(conn, newValidation);
130
                }
131
132
            } else {
133
                tmp = oldValidation;
134
                // Increase validation counter
135
                increaseValidationCounter(conn, oldValidation);
136
137
                // Delete obsolete errors, that is, errors from the previous
138
                // validation that have disappeared from the new validation
139
                // even though they belong to the same validation group
140
                dontDeleteErrorsInOtherValidationGroups(oldValidation, validationGroups);
141
                // Now all errors have been removed from the previous validation
142
                // that don't belong to the validation group(s) applied by the
143
                // current validation. Set them apart because we need them
144
                HashSet<EntityConstraintViolation> oldErrors = new HashSet<EntityConstraintViolation>(
145
                        oldValidation.getEntityConstraintViolations());
146
                oldValidation.getEntityConstraintViolations().removeAll(newValidation.getEntityConstraintViolations());
147
                // Now we're left with previous errors that have disappeared
148
                // from the current validation (they have become obsolete)
149
                deleteObsoleteErrors(conn, oldValidation);
150
151
                // From the new errors delete all that are identical to
152
                // errors from a previous validation (identical as per the
153
                // equals() method of EntityConstraintViolation). These
154
                // errors will not replace the old ones in order to limit
155
                // the number of INSERTs.
156
                newValidation.getEntityConstraintViolations().removeAll(oldErrors);
157
                saveErrorRecords(conn, newValidation);
158
            }
159
            conn.commit();
160
            setStatus(conn, tmp, EntityValidationStatus.OK);
161
        } catch (Throwable t) {
162
            logger.error("Error while saving validation result:", t);
163
            setStatus(conn, tmp, EntityValidationStatus.ERROR);
164
            JdbcDaoUtils.rollback(conn);
165
        } finally {
166
            JdbcDaoUtils.close(conn);
167
        }
168
    }
169
170
    @Override
171
    public void deleteEntityValidation(String validatedEntityClass, int validatedEntityId) {
172
        Connection conn = null;
173
        try {
174
            conn = datasource.getConnection();
175
            JdbcDaoUtils.startTransaction(conn);
176
            int validationResultId = getValidationResultId(conn, validatedEntityClass, validatedEntityId);
177
            if (validationResultId == -1) {
178
                return;
179
            }
180
            deleteValidationResultRecord(conn, validationResultId);
181
            deletedErrorRecords(conn, validationResultId, null);
182
            conn.commit();
183
        } catch (Throwable t) {
184
            JdbcDaoUtils.rollback(conn);
185
        }
186
        JdbcDaoUtils.close(conn);
187
    }
188
189
    private static <T extends ICdmBase> EntityValidation createEntityValidation(T validatedEntity,
190
            Set<ConstraintViolation<T>> errors, CRUDEventType crudEventType) {
191
        EntityValidation entityValidation = EntityValidation.newInstance(validatedEntity, crudEventType);
192
        Set<EntityConstraintViolation> errorEntities = new HashSet<EntityConstraintViolation>(errors.size());
193
        for (ConstraintViolation<T> error : errors) {
194
            EntityConstraintViolation errorEntity = EntityConstraintViolation.newInstance(validatedEntity, error);
195
            errorEntities.add(errorEntity);
196
        }
197
        entityValidation.setEntityConstraintViolations(errorEntities);
198
        return entityValidation;
199
    }
200
201
    private static void deletedErrorRecords(Connection conn, int validationResultId, Class<?>[] validationGroups)
202
            throws SQLException {
203
        StringBuilder sql = new StringBuilder(127);
204
        sql.append("DELETE FROM entityconstraintviolation WHERE entityvalidation_id = ?");
205
        if (validationGroups != null && validationGroups.length != 0) {
206
            sql.append(" AND (");
207
            for (int i = 0; i < validationGroups.length; ++i) {
208
                if (i != 0) {
209
                    sql.append(" OR ");
210
                }
211
                sql.append("validationgroup = ?");
212
            }
213
            sql.append(")");
214
        }
215
        PreparedStatement stmt = null;
216
        try {
217
            stmt = conn.prepareStatement(sql.toString());
218
            stmt.setInt(1, validationResultId);
219
            if (validationGroups != null && validationGroups.length != 0) {
220
                for (int i = 0; i < validationGroups.length; ++i) {
221
                    stmt.setString(i + 2, validationGroups[i].getName());
222
                }
223
            }
224
            stmt.executeUpdate();
225
        } finally {
226
            JdbcDaoUtils.close(stmt);
227
        }
228
    }
229
230
    private static void deleteObsoleteErrors(Connection conn, EntityValidation previousValidation) throws SQLException {
231
        Set<EntityConstraintViolation> obsoleteErrors = previousValidation.getEntityConstraintViolations();
232
        if (obsoleteErrors == null || obsoleteErrors.size() == 0) {
233
            return;
234
        }
235
        String sql = "DELETE FROM entityconstraintviolation WHERE id = ?";
236
        PreparedStatement stmt = null;
237
        try {
238
            stmt = conn.prepareStatement(sql.toString());
239
            for (EntityConstraintViolation error : obsoleteErrors) {
240
                stmt.setInt(1, error.getId());
241
                stmt.executeUpdate();
242
            }
243
        } finally {
244
            JdbcDaoUtils.close(stmt);
245
        }
246
    }
247
248
    // Save EntityValidation entity to database. As a side effect
249
    // the database id assigned to the entity will be set on the
250
    // EntityValidation instance
251
    private static void saveEntityValidationRecord(Connection conn, EntityValidation newValidation) throws SQLException {
252
        PreparedStatement stmt = null;
253
        try {
254
            stmt = conn.prepareStatement(SQL_INSERT_VALIDATION_RESULT);
255
            if (newValidation.getId() <= 0) {
256
                int id = 10 + JdbcDaoUtils.fetchInt(conn, "SELECT MAX(id) FROM entityvalidation");
257
                newValidation.setId(id);
258
            }
259
            stmt.setInt(vr_id, newValidation.getId());
260
            stmt.setDate(vr_created, new Date(newValidation.getCreated().getMillis()));
261
            stmt.setString(vr_uuid, newValidation.getUuid().toString());
262
            stmt.setString(vr_crudeventtype, newValidation.getCrudEventType().toString());
263
            stmt.setString(vr_validatedentityclass, newValidation.getValidatedEntityClass());
264
            stmt.setInt(vr_validatedentityid, newValidation.getValidatedEntityId());
265
            stmt.setString(vr_validatedentityuuid, newValidation.getValidatedEntityUuid().toString());
266
            stmt.setString(vr_userfriendlydescription, newValidation.getUserFriendlyDescription());
267
            stmt.setString(vr_userfriendlytypename, newValidation.getUserFriendlyTypeName());
268
            stmt.setInt(vr_validationcount, 1);
269
            stmt.setDate(vr_updated, new Date(newValidation.getCreated().getMillis()));
270
            stmt.setString(vr_status, EntityValidationStatus.IN_PROGRESS.toString());
271
            if (newValidation.getCreatedBy() != null) {
272
                stmt.setInt(vr_createdby_id, newValidation.getCreatedBy().getId());
273
            } else {
274
                stmt.setNull(vr_createdby_id, Types.INTEGER);
275
            }
276
            stmt.executeUpdate();
277
        } finally {
278
            JdbcDaoUtils.close(stmt);
279
        }
280
    }
281
282
    private static void increaseValidationCounter(Connection conn, EntityValidation entityValidation)
283
            throws SQLException {
284
        String sql = "UPDATE entityvalidation SET crudeventtype=?, validationcount = validationcount + 1, "
285
                + "updated = ?, status = ? WHERE id=?";
286
        PreparedStatement stmt = null;
287
        try {
288
            stmt = conn.prepareStatement(sql);
289
            if (entityValidation.getCrudEventType() == null) {
290
                stmt.setString(1, null);
291
            } else {
292
                stmt.setString(1, entityValidation.getCrudEventType().toString());
293
            }
294
            stmt.setDate(2, new Date(new java.util.Date().getTime()));
295
            stmt.setString(3, EntityValidationStatus.IN_PROGRESS.toString());
296
            stmt.setInt(4, entityValidation.getId());
297
            stmt.executeUpdate();
298
        } finally {
299
            JdbcDaoUtils.close(stmt);
300
        }
301
    }
302
303
    private static <T extends ICdmBase> void saveErrorRecords(Connection conn, EntityValidation entityValidation)
304
            throws SQLException {
305
        Set<EntityConstraintViolation> errors = entityValidation.getEntityConstraintViolations();
306
        if (errors == null || errors.size() == 0) {
307
            return;
308
        }
309
        PreparedStatement stmt = null;
310
        try {
311
            stmt = conn.prepareStatement(SQL_INSERT_CONSTRAINT_VIOLATION);
312
            for (EntityConstraintViolation error : errors) {
313
                if (error.getId() <= 0) {
314
                    int id = 10 + JdbcDaoUtils.fetchInt(conn, "SELECT MAX(id) FROM entityconstraintviolation");
315
                    error.setId(id);
316
                }
317
                stmt.setInt(cv_id, error.getId());
318
                stmt.setDate(cv_created, new Date(error.getCreated().getMillis()));
319
                stmt.setString(cv_uuid, error.getUuid().toString());
320
                stmt.setString(cv_invalidvalue, error.getInvalidValue());
321
                stmt.setString(cv_message, error.getMessage());
322
                stmt.setString(cv_propertypath, error.getPropertyPath());
323
                stmt.setString(cv_userfriendlyfieldname, error.getUserFriendlyFieldName());
324
                stmt.setString(cv_severity, error.getSeverity().toString());
325
                stmt.setString(cv_validator, error.getValidator());
326
                stmt.setString(cv_validationgroup, error.getValidationGroup());
327
                if (error.getCreatedBy() != null) {
328
                    stmt.setInt(cv_createdby_id, error.getCreatedBy().getId());
329
                } else {
330
                    stmt.setNull(cv_createdby_id, Types.INTEGER);
331
                }
332
                stmt.setInt(cv_entityvalidation_id, entityValidation.getId());
333
                stmt.executeUpdate();
334
            }
335
        } finally {
336
            JdbcDaoUtils.close(stmt);
337
        }
338
    }
339
340
    // Called by unit test
341
    EntityValidation getEntityValidation(String validatedEntityClass, int validatedEntityId) {
342
        Connection conn = null;
343
        try {
344
            conn = datasource.getConnection();
345
            JdbcDaoUtils.startTransaction(conn);
346
            EntityValidation result = getEntityValidation(conn, validatedEntityClass, validatedEntityId);
347
            conn.commit();
348
            return result;
349
        } catch (Throwable t) {
350
            logger.error("Error while retrieving validation result", t);
351
            JdbcDaoUtils.rollback(conn);
352
            return null;
353
        }
354
    }
355
356
    private static EntityValidation getEntityValidation(Connection conn, String validatedEntityClass,
357
            int validatedEntityId) throws SQLException {
358
        EntityValidation entityValidation = getEntityValidationRecord(conn, validatedEntityClass, validatedEntityId);
359
        if (entityValidation != null) {
360
            entityValidation.setEntityConstraintViolations(getErrorRecords(conn, entityValidation.getId()));
361
        }
362
        return entityValidation;
363
    }
364
365
    private static void deleteValidationResultRecord(Connection conn, int validationResultId) throws SQLException {
366
        String sql = "DELETE FROM entityvalidation WHERE id = ?";
367
        PreparedStatement stmt = conn.prepareStatement(sql);
368
        stmt.setInt(1, validationResultId);
369
        stmt.executeUpdate();
370
    }
371
372
    private static void setStatus(Connection conn, EntityValidation entityValidation, EntityValidationStatus status) {
373
        if (conn == null || entityValidation == null || entityValidation.getId() <= 0) {
374
            logger.warn("Failed to save entity validation status to database");
375
            return;
376
        }
377
        String sql = "UPDATE entityvalidation SET status = ? WHERE id = ?";
378
        PreparedStatement stmt = null;
379
        try {
380
            JdbcDaoUtils.startTransaction(conn);
381
            stmt = conn.prepareStatement(sql);
382
            stmt.setString(1, status.toString());
383
            stmt.setInt(2, entityValidation.getId());
384
            stmt.executeUpdate();
385
            conn.commit();
386
        } catch (Throwable t) {
387
            logger.error("Failed to set validation status", t);
388
        } finally {
389
            JdbcDaoUtils.close(stmt);
390
        }
391
    }
392
393
    private static <T extends ICdmBase> EntityValidation getEntityValidationRecord(Connection conn,
394
            String validatedEntityClass, int validatedEntityId) throws SQLException {
395
        String sqlCount = "SELECT count(*) as n FROM entityvalidation";
396
        PreparedStatement stmtCount = conn.prepareStatement(sqlCount);
397
        ResultSet rsCount = stmtCount.executeQuery();
398
        if (rsCount.next()) {
399
            int n = rsCount.getInt("n");
400
            System.out.println("count=" + n);
401
        }
402
403
        String sql = "SELECT * FROM entityvalidation WHERE validatedentityclass=? AND validatedentityid=?";
404
        EntityValidation result = null;
405
        PreparedStatement stmt = null;
406
        try {
407
            stmt = conn.prepareStatement(sql);
408
            stmt.setString(1, validatedEntityClass);
409
            stmt.setInt(2, validatedEntityId);
410
            ResultSet rs = stmt.executeQuery();
411
            if (rs.next()) {
412
                result = EntityValidation.newInstance();
413
                result.setId(rs.getInt("id"));
414
                Date d = rs.getDate("created");
415
                if (!rs.wasNull()) {
416
                    result.setCreated(new DateTime(d.getTime()));
417
                }
418
                String s = rs.getString("uuid");
419
                if (!rs.wasNull()) {
420
                    result.setUuid(UUID.fromString(rs.getString("uuid")));
421
                }
422
                s = rs.getString("crudeventtype");
423
                if (!rs.wasNull()) {
424
                    result.setCrudEventType(CRUDEventType.valueOf(s));
425
                }
426
                result.setValidatedEntityClass(rs.getString("validatedentityclass"));
427
                result.setValidatedEntityId(rs.getInt("validatedentityid"));
428
                s = rs.getString("validatedentityuuid");
429
                if (!rs.wasNull()) {
430
                    result.setValidatedEntityUuid(UUID.fromString(s));
431
                }
432
                result.setUserFriendlyDescription(rs.getString("userfriendlydescription"));
433
                result.setUserFriendlyTypeName(rs.getString("userfriendlytypename"));
434
            }
435
            rs.close();
436
            return result;
437
        } finally {
438
            JdbcDaoUtils.close(stmt);
439
        }
440
    }
441
442
    private static int getValidationResultId(Connection conn, String validatedEntityClass, int validatedEntityId)
443
            throws SQLException {
444
        String sql = "SELECT id FROM entityvalidation WHERE validatedentityclass = ? AND validatedentityid = ?";
445
        PreparedStatement stmt = null;
446
        int result = -1;
447
        try {
448
            stmt = conn.prepareStatement(sql);
449
            stmt.setString(1, validatedEntityClass);
450
            stmt.setInt(2, validatedEntityId);
451
            ResultSet rs = stmt.executeQuery();
452
            if (rs.next()) {
453
                result = rs.getInt(1);
454
            }
455
            rs.close();
456
        } finally {
457
            JdbcDaoUtils.close(stmt);
458
        }
459
        return result;
460
    }
461
462
    private static Set<EntityConstraintViolation> getErrorRecords(Connection conn, int entityValidationId)
463
            throws SQLException {
464
        return getErrorRecordsForValidationGroup(conn, entityValidationId, null);
465
    }
466
467
    private static Set<EntityConstraintViolation> getErrorRecordsForValidationGroup(Connection conn,
468
            int entityValidationId, Class<?>[] validationGroups) throws SQLException {
469
        StringBuilder sql = new StringBuilder("SELECT * FROM entityconstraintviolation WHERE entityvalidation_id=?");
470
        if (validationGroups != null && validationGroups.length != 0) {
471
            sql.append(" AND (");
472
            for (int i = 0; i < validationGroups.length; ++i) {
473
                if (i != 0) {
474
                    sql.append(" OR ");
475
                }
476
                sql.append("validationgroup = ?");
477
            }
478
            sql.append(")");
479
        }
480
        PreparedStatement stmt = null;
481
        Set<EntityConstraintViolation> errors = new HashSet<EntityConstraintViolation>();
482
        try {
483
            stmt = conn.prepareStatement(sql.toString());
484
            stmt.setInt(1, entityValidationId);
485
            if (validationGroups != null && validationGroups.length != 0) {
486
                for (int i = 0; i < validationGroups.length; ++i) {
487
                    stmt.setString(i + 2, validationGroups[i].getName());
488
                }
489
            }
490
            ResultSet rs = stmt.executeQuery();
491
            while (rs.next()) {
492
                EntityConstraintViolation error = EntityConstraintViolation.newInstance();
493
                error.setId(rs.getInt("id"));
494
                error.setCreated(new DateTime(rs.getDate("created").getTime()));
495
                error.setUuid(UUID.fromString(rs.getString("uuid")));
496
                error.setInvalidValue(rs.getString("invalidvalue"));
497
                error.setMessage(rs.getString("message"));
498
                error.setPropertyPath(rs.getString("propertypath"));
499
                error.setUserFriendlyFieldName(rs.getString("userfriendlyfieldname"));
500
                error.setSeverity(Severity.forName(rs.getString("severity")));
501
                error.setValidator(rs.getString("validator"));
502
                error.setValidationGroup(rs.getString("validationgroup"));
503
                errors.add(error);
504
            }
505
            rs.close();
506
        } finally {
507
            JdbcDaoUtils.close(stmt);
508
        }
509
        return errors;
510
    }
511
512
    private static void dontDeleteErrorsInOtherValidationGroups(EntityValidation previousValidation,
513
            Class<?>[] validationGroups) {
514 379eb203 Andreas Müller
        Set<String> classNames = new HashSet<>(validationGroups.length);
515 3dd6c00e Andreas Müller
        for (Class<?> c : validationGroups) {
516
            classNames.add(c.getName());
517
        }
518
        Iterator<EntityConstraintViolation> iterator = previousValidation.getEntityConstraintViolations().iterator();
519
        while (iterator.hasNext()) {
520
            if (!classNames.contains(iterator.next().getValidationGroup())) {
521
                iterator.remove();
522
            }
523
        }
524
    }
525
}