Project

General

Profile

Download (23.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
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
import org.apache.log4j.Logger;
26
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
 * @date 16 jan. 2015
42
 *
43
 */
44
@Repository
45
public class EntityValidationCrudJdbcImpl implements IEntityValidationCrud {
46

    
47
    public static final Logger logger = Logger.getLogger(EntityValidationCrudJdbcImpl.class);
48

    
49
    private static final String SQL_INSERT_VALIDATION_RESULT = "INSERT INTO entityvalidation"
50
            + "(id, created, uuid,  crudeventtype, validatedentityclass, validatedentityid,"
51
            + "validatedentityuuid, userfriendlydescription, userfriendlytypename, validationcount,"
52
            + "updated, status, createdby_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";
53

    
54
    private static final int vr_id = 1;
55
    private static final int vr_created = 2;
56
    private static final int vr_uuid = 3;
57
    private static final int vr_crudeventtype = 4;
58
    private static final int vr_validatedentityclass = 5;
59
    private static final int vr_validatedentityid = 6;
60
    private static final int vr_validatedentityuuid = 7;
61
    private static final int vr_userfriendlydescription = 8;
62
    private static final int vr_userfriendlytypename = 9;
63
    private static final int vr_validationcount = 10;
64
    private static final int vr_updated = 11;
65
    private static final int vr_status = 12;
66
    private static final int vr_createdby_id = 13;
67

    
68
    private static final String SQL_INSERT_CONSTRAINT_VIOLATION = "INSERT INTO entityconstraintviolation"
69
            + "(id, created, uuid,  invalidvalue, message, propertypath, userfriendlyfieldname, severity,"
70
            + "validator, validationgroup, createdby_id, entityvalidation_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
71

    
72
    private static final int cv_id = 1;
73
    private static final int cv_created = 2;
74
    private static final int cv_uuid = 3;
75
    private static final int cv_invalidvalue = 4;
76
    private static final int cv_message = 5;
77
    private static final int cv_propertypath = 6;
78
    private static final int cv_userfriendlyfieldname = 7;
79
    private static final int cv_severity = 8;
80
    private static final int cv_validator = 9;
81
    private static final int cv_validationgroup = 10;
82
    private static final int cv_createdby_id = 11;
83
    private static final int cv_entityvalidation_id = 12;
84

    
85
    @Autowired
86
    private DataSource datasource;
87

    
88
    public EntityValidationCrudJdbcImpl() {
89

    
90
    }
91

    
92
    public EntityValidationCrudJdbcImpl(DataSource datasource) {
93
        this.datasource = datasource;
94
    }
95

    
96
    public void setDatasource(DataSource datasource) {
97
        this.datasource = datasource;
98
    }
99

    
100
    @Override
101
    public <T extends ICdmBase> void saveEntityValidation(T validatedEntity, Set<ConstraintViolation<T>> errors,
102
            CRUDEventType crudEventType, Class<?>[] validationGroups) {
103
        saveEntityValidation(createEntityValidation(validatedEntity, errors, crudEventType), validationGroups);
104
    }
105

    
106
    // This is the method that's tested by the unit tests
107
    // rather than the interface method above, because it
108
    // is almost impossible to create a mock instance of
109
    // ConstraintViolation<T>
110
    void saveEntityValidation(EntityValidation newValidation, Class<?>[] validationGroups) {
111
        Connection conn = null;
112
        EntityValidation tmp = null;
113
        try {
114
            conn = datasource.getConnection();
115
            JdbcDaoUtils.startTransaction(conn);
116
            String entityClass = newValidation.getValidatedEntityClass();
117
            int entityId = newValidation.getValidatedEntityId();
118
            EntityValidation oldValidation = getEntityValidation(conn, entityClass, entityId);
119
            if (oldValidation == null) {
120
                tmp = newValidation;
121
                /*
122
                 * The entity has never been validated before. We should now
123
                 * create an entityvalidation record whether or not the entity
124
                 * has errors, because the entity HAS been validated so its
125
                 * validationcount is now 1.
126
                 */
127
                saveEntityValidationRecord(conn, newValidation);
128
                Set<EntityConstraintViolation> errors = newValidation.getEntityConstraintViolations();
129
                if (errors != null && errors.size() != 0) {
130
                    saveErrorRecords(conn, newValidation);
131
                }
132

    
133
            } else {
134
                tmp = oldValidation;
135
                // Increase validation counter
136
                increaseValidationCounter(conn, oldValidation);
137

    
138
                // Delete obsolete errors, that is, errors from the previous
139
                // validation that have disappeared from the new validation
140
                // even though they belong to the same validation group
141
                dontDeleteErrorsInOtherValidationGroups(oldValidation, validationGroups);
142
                // Now all errors have been removed from the previous validation
143
                // that don't belong to the validation group(s) applied by the
144
                // current validation. Set them apart because we need them
145
                HashSet<EntityConstraintViolation> oldErrors = new HashSet<EntityConstraintViolation>(
146
                        oldValidation.getEntityConstraintViolations());
147
                oldValidation.getEntityConstraintViolations().removeAll(newValidation.getEntityConstraintViolations());
148
                // Now we're left with previous errors that have disappeared
149
                // from the current validation (they have become obsolete)
150
                deleteObsoleteErrors(conn, oldValidation);
151

    
152
                // From the new errors delete all that are identical to
153
                // errors from a previous validation (identical as per the
154
                // equals() method of EntityConstraintViolation). These
155
                // errors will not replace the old ones in order to limit
156
                // the number of INSERTs.
157
                newValidation.getEntityConstraintViolations().removeAll(oldErrors);
158
                saveErrorRecords(conn, newValidation);
159
            }
160
            conn.commit();
161
            setStatus(conn, tmp, EntityValidationStatus.OK);
162
        } catch (Throwable t) {
163
            logger.error("Error while saving validation result:", t);
164
            setStatus(conn, tmp, EntityValidationStatus.ERROR);
165
            JdbcDaoUtils.rollback(conn);
166
        } finally {
167
            JdbcDaoUtils.close(conn);
168
        }
169
    }
170

    
171
    @Override
172
    public void deleteEntityValidation(String validatedEntityClass, int validatedEntityId) {
173
        Connection conn = null;
174
        try {
175
            conn = datasource.getConnection();
176
            JdbcDaoUtils.startTransaction(conn);
177
            int validationResultId = getValidationResultId(conn, validatedEntityClass, validatedEntityId);
178
            if (validationResultId == -1) {
179
                return;
180
            }
181
            deleteValidationResultRecord(conn, validationResultId);
182
            deletedErrorRecords(conn, validationResultId, null);
183
            conn.commit();
184
        } catch (Throwable t) {
185
            JdbcDaoUtils.rollback(conn);
186
        }
187
        JdbcDaoUtils.close(conn);
188
    }
189

    
190
    private static <T extends ICdmBase> EntityValidation createEntityValidation(T validatedEntity,
191
            Set<ConstraintViolation<T>> errors, CRUDEventType crudEventType) {
192
        EntityValidation entityValidation = EntityValidation.newInstance(validatedEntity, crudEventType);
193
        Set<EntityConstraintViolation> errorEntities = new HashSet<EntityConstraintViolation>(errors.size());
194
        for (ConstraintViolation<T> error : errors) {
195
            EntityConstraintViolation errorEntity = EntityConstraintViolation.newInstance(validatedEntity, error);
196
            errorEntities.add(errorEntity);
197
        }
198
        entityValidation.setEntityConstraintViolations(errorEntities);
199
        return entityValidation;
200
    }
201

    
202
    private static void deletedErrorRecords(Connection conn, int validationResultId, Class<?>[] validationGroups)
203
            throws SQLException {
204
        StringBuilder sql = new StringBuilder(127);
205
        sql.append("DELETE FROM entityconstraintviolation WHERE entityvalidation_id = ?");
206
        if (validationGroups != null && validationGroups.length != 0) {
207
            sql.append(" AND (");
208
            for (int i = 0; i < validationGroups.length; ++i) {
209
                if (i != 0) {
210
                    sql.append(" OR ");
211
                }
212
                sql.append("validationgroup = ?");
213
            }
214
            sql.append(")");
215
        }
216
        PreparedStatement stmt = null;
217
        try {
218
            stmt = conn.prepareStatement(sql.toString());
219
            stmt.setInt(1, validationResultId);
220
            if (validationGroups != null && validationGroups.length != 0) {
221
                for (int i = 0; i < validationGroups.length; ++i) {
222
                    stmt.setString(i + 2, validationGroups[i].getName());
223
                }
224
            }
225
            stmt.executeUpdate();
226
        } finally {
227
            JdbcDaoUtils.close(stmt);
228
        }
229
    }
230

    
231
    private static void deleteObsoleteErrors(Connection conn, EntityValidation previousValidation) throws SQLException {
232
        Set<EntityConstraintViolation> obsoleteErrors = previousValidation.getEntityConstraintViolations();
233
        if (obsoleteErrors == null || obsoleteErrors.size() == 0) {
234
            return;
235
        }
236
        String sql = "DELETE FROM entityconstraintviolation WHERE id = ?";
237
        PreparedStatement stmt = null;
238
        try {
239
            stmt = conn.prepareStatement(sql.toString());
240
            for (EntityConstraintViolation error : obsoleteErrors) {
241
                stmt.setInt(1, error.getId());
242
                stmt.executeUpdate();
243
            }
244
        } finally {
245
            JdbcDaoUtils.close(stmt);
246
        }
247
    }
248

    
249
    // Save EntityValidation entity to database. As a side effect
250
    // the database id assigned to the entity will be set on the
251
    // EntityValidation instance
252
    private static void saveEntityValidationRecord(Connection conn, EntityValidation newValidation) throws SQLException {
253
        PreparedStatement stmt = null;
254
        try {
255
            stmt = conn.prepareStatement(SQL_INSERT_VALIDATION_RESULT);
256
            if (newValidation.getId() <= 0) {
257
                int id = 10 + JdbcDaoUtils.fetchInt(conn, "SELECT MAX(id) FROM entityvalidation");
258
                newValidation.setId(id);
259
            }
260
            stmt.setInt(vr_id, newValidation.getId());
261
            stmt.setDate(vr_created, new Date(newValidation.getCreated().getMillis()));
262
            stmt.setString(vr_uuid, newValidation.getUuid().toString());
263
            stmt.setString(vr_crudeventtype, newValidation.getCrudEventType().toString());
264
            stmt.setString(vr_validatedentityclass, newValidation.getValidatedEntityClass());
265
            stmt.setInt(vr_validatedentityid, newValidation.getValidatedEntityId());
266
            stmt.setString(vr_validatedentityuuid, newValidation.getValidatedEntityUuid().toString());
267
            stmt.setString(vr_userfriendlydescription, newValidation.getUserFriendlyDescription());
268
            stmt.setString(vr_userfriendlytypename, newValidation.getUserFriendlyTypeName());
269
            stmt.setInt(vr_validationcount, 1);
270
            stmt.setDate(vr_updated, new Date(newValidation.getCreated().getMillis()));
271
            stmt.setString(vr_status, EntityValidationStatus.IN_PROGRESS.toString());
272
            if (newValidation.getCreatedBy() != null) {
273
                stmt.setInt(vr_createdby_id, newValidation.getCreatedBy().getId());
274
            } else {
275
                stmt.setNull(vr_createdby_id, Types.INTEGER);
276
            }
277
            stmt.executeUpdate();
278
        } finally {
279
            JdbcDaoUtils.close(stmt);
280
        }
281
    }
282

    
283
    private static void increaseValidationCounter(Connection conn, EntityValidation entityValidation)
284
            throws SQLException {
285
        String sql = "UPDATE entityvalidation SET crudeventtype=?, validationcount = validationcount + 1, "
286
                + "updated = ?, status = ? WHERE id=?";
287
        PreparedStatement stmt = null;
288
        try {
289
            stmt = conn.prepareStatement(sql);
290
            if (entityValidation.getCrudEventType() == null) {
291
                stmt.setString(1, null);
292
            } else {
293
                stmt.setString(1, entityValidation.getCrudEventType().toString());
294
            }
295
            stmt.setDate(2, new Date(new java.util.Date().getTime()));
296
            stmt.setString(3, EntityValidationStatus.IN_PROGRESS.toString());
297
            stmt.setInt(4, entityValidation.getId());
298
            stmt.executeUpdate();
299
        } finally {
300
            JdbcDaoUtils.close(stmt);
301
        }
302
    }
303

    
304
    private static <T extends ICdmBase> void saveErrorRecords(Connection conn, EntityValidation entityValidation)
305
            throws SQLException {
306
        Set<EntityConstraintViolation> errors = entityValidation.getEntityConstraintViolations();
307
        if (errors == null || errors.size() == 0) {
308
            return;
309
        }
310
        PreparedStatement stmt = null;
311
        try {
312
            stmt = conn.prepareStatement(SQL_INSERT_CONSTRAINT_VIOLATION);
313
            for (EntityConstraintViolation error : errors) {
314
                if (error.getId() <= 0) {
315
                    int id = 10 + JdbcDaoUtils.fetchInt(conn, "SELECT MAX(id) FROM entityconstraintviolation");
316
                    error.setId(id);
317
                }
318
                stmt.setInt(cv_id, error.getId());
319
                stmt.setDate(cv_created, new Date(error.getCreated().getMillis()));
320
                stmt.setString(cv_uuid, error.getUuid().toString());
321
                stmt.setString(cv_invalidvalue, error.getInvalidValue());
322
                stmt.setString(cv_message, error.getMessage());
323
                stmt.setString(cv_propertypath, error.getPropertyPath());
324
                stmt.setString(cv_userfriendlyfieldname, error.getUserFriendlyFieldName());
325
                stmt.setString(cv_severity, error.getSeverity().toString());
326
                stmt.setString(cv_validator, error.getValidator());
327
                stmt.setString(cv_validationgroup, error.getValidationGroup());
328
                if (error.getCreatedBy() != null) {
329
                    stmt.setInt(cv_createdby_id, error.getCreatedBy().getId());
330
                } else {
331
                    stmt.setNull(cv_createdby_id, Types.INTEGER);
332
                }
333
                stmt.setInt(cv_entityvalidation_id, entityValidation.getId());
334
                stmt.executeUpdate();
335
            }
336
        } finally {
337
            JdbcDaoUtils.close(stmt);
338
        }
339
    }
340

    
341
    // Called by unit test
342
    EntityValidation getEntityValidation(String validatedEntityClass, int validatedEntityId) {
343
        Connection conn = null;
344
        try {
345
            conn = datasource.getConnection();
346
            JdbcDaoUtils.startTransaction(conn);
347
            EntityValidation result = getEntityValidation(conn, validatedEntityClass, validatedEntityId);
348
            conn.commit();
349
            return result;
350
        } catch (Throwable t) {
351
            logger.error("Error while retrieving validation result", t);
352
            JdbcDaoUtils.rollback(conn);
353
            return null;
354
        }
355
    }
356

    
357
    private static EntityValidation getEntityValidation(Connection conn, String validatedEntityClass,
358
            int validatedEntityId) throws SQLException {
359
        EntityValidation entityValidation = getEntityValidationRecord(conn, validatedEntityClass, validatedEntityId);
360
        if (entityValidation != null) {
361
            entityValidation.setEntityConstraintViolations(getErrorRecords(conn, entityValidation.getId()));
362
        }
363
        return entityValidation;
364
    }
365

    
366
    private static void deleteValidationResultRecord(Connection conn, int validationResultId) throws SQLException {
367
        String sql = "DELETE FROM entityvalidation WHERE id = ?";
368
        PreparedStatement stmt = conn.prepareStatement(sql);
369
        stmt.setInt(1, validationResultId);
370
        stmt.executeUpdate();
371
    }
372

    
373
    private static void setStatus(Connection conn, EntityValidation entityValidation, EntityValidationStatus status) {
374
        if (conn == null || entityValidation == null || entityValidation.getId() <= 0) {
375
            logger.warn("Failed to save entity validation status to database");
376
            return;
377
        }
378
        String sql = "UPDATE entityvalidation SET status = ? WHERE id = ?";
379
        PreparedStatement stmt = null;
380
        try {
381
            JdbcDaoUtils.startTransaction(conn);
382
            stmt = conn.prepareStatement(sql);
383
            stmt.setString(1, status.toString());
384
            stmt.setInt(2, entityValidation.getId());
385
            stmt.executeUpdate();
386
            conn.commit();
387
        } catch (Throwable t) {
388
            logger.error("Failed to set validation status", t);
389
        } finally {
390
            JdbcDaoUtils.close(stmt);
391
        }
392
    }
393

    
394
    private static <T extends ICdmBase> EntityValidation getEntityValidationRecord(Connection conn,
395
            String validatedEntityClass, int validatedEntityId) throws SQLException {
396
        String sqlCount = "SELECT count(*) as n FROM entityvalidation";
397
        PreparedStatement stmtCount = conn.prepareStatement(sqlCount);
398
        ResultSet rsCount = stmtCount.executeQuery();
399
        if (rsCount.next()) {
400
            int n = rsCount.getInt("n");
401
            System.out.println("count=" + n);
402
        }
403

    
404
        String sql = "SELECT * FROM entityvalidation WHERE validatedentityclass=? AND validatedentityid=?";
405
        EntityValidation result = null;
406
        PreparedStatement stmt = null;
407
        try {
408
            stmt = conn.prepareStatement(sql);
409
            stmt.setString(1, validatedEntityClass);
410
            stmt.setInt(2, validatedEntityId);
411
            ResultSet rs = stmt.executeQuery();
412
            if (rs.next()) {
413
                result = EntityValidation.newInstance();
414
                result.setId(rs.getInt("id"));
415
                Date d = rs.getDate("created");
416
                if (!rs.wasNull()) {
417
                    result.setCreated(new DateTime(d.getTime()));
418
                }
419
                String s = rs.getString("uuid");
420
                if (!rs.wasNull()) {
421
                    result.setUuid(UUID.fromString(rs.getString("uuid")));
422
                }
423
                s = rs.getString("crudeventtype");
424
                if (!rs.wasNull()) {
425
                    result.setCrudEventType(CRUDEventType.valueOf(s));
426
                }
427
                result.setValidatedEntityClass(rs.getString("validatedentityclass"));
428
                result.setValidatedEntityId(rs.getInt("validatedentityid"));
429
                s = rs.getString("validatedentityuuid");
430
                if (!rs.wasNull()) {
431
                    result.setValidatedEntityUuid(UUID.fromString(s));
432
                }
433
                result.setUserFriendlyDescription(rs.getString("userfriendlydescription"));
434
                result.setUserFriendlyTypeName(rs.getString("userfriendlytypename"));
435
            }
436
            rs.close();
437
            return result;
438
        } finally {
439
            JdbcDaoUtils.close(stmt);
440
        }
441
    }
442

    
443
    private static int getValidationResultId(Connection conn, String validatedEntityClass, int validatedEntityId)
444
            throws SQLException {
445
        String sql = "SELECT id FROM entityvalidation WHERE validatedentityclass = ? AND validatedentityid = ?";
446
        PreparedStatement stmt = null;
447
        int result = -1;
448
        try {
449
            stmt = conn.prepareStatement(sql);
450
            stmt.setString(1, validatedEntityClass);
451
            stmt.setInt(2, validatedEntityId);
452
            ResultSet rs = stmt.executeQuery();
453
            if (rs.next()) {
454
                result = rs.getInt(1);
455
            }
456
            rs.close();
457
        } finally {
458
            JdbcDaoUtils.close(stmt);
459
        }
460
        return result;
461
    }
462

    
463
    private static Set<EntityConstraintViolation> getErrorRecords(Connection conn, int entityValidationId)
464
            throws SQLException {
465
        return getErrorRecordsForValidationGroup(conn, entityValidationId, null);
466
    }
467

    
468
    private static Set<EntityConstraintViolation> getErrorRecordsForValidationGroup(Connection conn,
469
            int entityValidationId, Class<?>[] validationGroups) throws SQLException {
470
        StringBuilder sql = new StringBuilder("SELECT * FROM entityconstraintviolation WHERE entityvalidation_id=?");
471
        if (validationGroups != null && validationGroups.length != 0) {
472
            sql.append(" AND (");
473
            for (int i = 0; i < validationGroups.length; ++i) {
474
                if (i != 0) {
475
                    sql.append(" OR ");
476
                }
477
                sql.append("validationgroup = ?");
478
            }
479
            sql.append(")");
480
        }
481
        PreparedStatement stmt = null;
482
        Set<EntityConstraintViolation> errors = new HashSet<EntityConstraintViolation>();
483
        try {
484
            stmt = conn.prepareStatement(sql.toString());
485
            stmt.setInt(1, entityValidationId);
486
            if (validationGroups != null && validationGroups.length != 0) {
487
                for (int i = 0; i < validationGroups.length; ++i) {
488
                    stmt.setString(i + 2, validationGroups[i].getName());
489
                }
490
            }
491
            ResultSet rs = stmt.executeQuery();
492
            while (rs.next()) {
493
                EntityConstraintViolation error = EntityConstraintViolation.newInstance();
494
                error.setId(rs.getInt("id"));
495
                error.setCreated(new DateTime(rs.getDate("created").getTime()));
496
                error.setUuid(UUID.fromString(rs.getString("uuid")));
497
                error.setInvalidValue(rs.getString("invalidvalue"));
498
                error.setMessage(rs.getString("message"));
499
                error.setPropertyPath(rs.getString("propertypath"));
500
                error.setUserFriendlyFieldName(rs.getString("userfriendlyfieldname"));
501
                error.setSeverity(Severity.forName(rs.getString("severity")));
502
                error.setValidator(rs.getString("validator"));
503
                error.setValidationGroup(rs.getString("validationgroup"));
504
                errors.add(error);
505
            }
506
            rs.close();
507
        } finally {
508
            JdbcDaoUtils.close(stmt);
509
        }
510
        return errors;
511
    }
512

    
513
    private static void dontDeleteErrorsInOtherValidationGroups(EntityValidation previousValidation,
514
            Class<?>[] validationGroups) {
515
        Set<String> classNames = new HashSet<String>(validationGroups.length);
516
        for (Class<?> c : validationGroups) {
517
            classNames.add(c.getName());
518
        }
519
        Iterator<EntityConstraintViolation> iterator = previousValidation.getEntityConstraintViolations().iterator();
520
        while (iterator.hasNext()) {
521
            if (!classNames.contains(iterator.next().getValidationGroup())) {
522
                iterator.remove();
523
            }
524
        }
525
    }
526

    
527
}
    (1-1/1)