001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.kernel.impl.services; 019 020import static org.slf4j.LoggerFactory.getLogger; 021 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.sql.Timestamp; 025import java.time.Instant; 026import java.util.List; 027import java.util.Map; 028import java.util.stream.Stream; 029 030import javax.annotation.PostConstruct; 031import javax.inject.Inject; 032import javax.sql.DataSource; 033import javax.transaction.Transactional; 034import org.apache.jena.graph.Node; 035import org.apache.jena.graph.NodeFactory; 036import org.apache.jena.graph.Triple; 037import org.fcrepo.common.db.DbPlatform; 038import org.fcrepo.kernel.api.identifiers.FedoraId; 039import org.slf4j.Logger; 040import org.springframework.core.io.DefaultResourceLoader; 041import org.springframework.jdbc.core.RowCallbackHandler; 042import org.springframework.jdbc.core.RowMapper; 043import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 044import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; 045import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; 046import org.springframework.stereotype.Component; 047 048import com.google.common.base.Preconditions; 049 050/** 051 * Manager for the membership index 052 * 053 * @author bbpennel 054 */ 055@Component 056public class MembershipIndexManager { 057 private static final Logger log = getLogger(MembershipIndexManager.class); 058 059 private static final Timestamp NO_END_TIMESTAMP = Timestamp.from(MembershipServiceImpl.NO_END_INSTANT); 060 private static final Timestamp NO_START_TIMESTAMP = Timestamp.from(Instant.parse("1000-01-01T00:00:00.000Z")); 061 062 private static final String ADD_OPERATION = "add"; 063 private static final String DELETE_OPERATION = "delete"; 064 private static final String FORCE_FLAG = "force"; 065 066 private static final String TX_ID_PARAM = "txId"; 067 private static final String SUBJECT_ID_PARAM = "subjectId"; 068 private static final String NO_END_TIME_PARAM = "noEndTime"; 069 private static final String ADD_OP_PARAM = "addOp"; 070 private static final String DELETE_OP_PARAM = "deleteOp"; 071 private static final String MEMENTO_TIME_PARAM = "mementoTime"; 072 private static final String PROPERTY_PARAM = "property"; 073 private static final String TARGET_ID_PARAM = "targetId"; 074 private static final String SOURCE_ID_PARAM = "sourceId"; 075 private static final String START_TIME_PARAM = "startTime"; 076 private static final String END_TIME_PARAM = "endTime"; 077 private static final String OPERATION_PARAM = "operation"; 078 private static final String FORCE_PARAM = "forceFlag"; 079 private static final String OBJECT_ID_PARAM = "objectId"; 080 081 private static final String SELECT_ALL_MEMBERSHIP = "SELECT * FROM membership"; 082 083 private static final String SELECT_ALL_OPERATIONS = "SELECT * FROM membership_tx_operations"; 084 085 private static final String SELECT_MEMBERSHIP_IN_TX = 086 "SELECT m.property, m.object_id" + 087 " FROM membership m" + 088 " WHERE subject_id = :subjectId" + 089 " AND end_time = :noEndTime" + 090 " AND NOT EXISTS (" + 091 " SELECT 1" + 092 " FROM membership_tx_operations mto" + 093 " WHERE mto.subject_id = :subjectId" + 094 " AND mto.source_id = m.source_id" + 095 " AND mto.object_id = m.object_id" + 096 " AND mto.tx_id = :txId" + 097 " AND mto.operation = :deleteOp)" + 098 " UNION" + 099 " SELECT property, object_id" + 100 " FROM membership_tx_operations" + 101 " WHERE subject_id = :subjectId" + 102 " AND tx_id = :txId" + 103 " AND end_time = :noEndTime" + 104 " AND operation = :addOp"; 105 106 private static final String SELECT_MEMBERSHIP_MEMENTO_IN_TX = 107 "SELECT property, object_id" + 108 " FROM membership m" + 109 " WHERE m.subject_id = :subjectId" + 110 " AND m.start_time <= :mementoTime" + 111 " AND m.end_time > :mementoTime" + 112 " AND NOT EXISTS (" + 113 " SELECT 1" + 114 " FROM membership_tx_operations mto" + 115 " WHERE mto.subject_id = :subjectId" + 116 " AND mto.source_id = m.source_id" + 117 " AND mto.property = m.property" + 118 " AND mto.object_id = m.object_id" + 119 " AND mto.end_time <= :mementoTime" + 120 " AND mto.tx_id = :txId" + 121 " AND mto.operation = :deleteOp)" + 122 " UNION" + 123 " SELECT property, object_id" + 124 " FROM membership_tx_operations" + 125 " WHERE subject_id = :subjectId" + 126 " AND tx_id = :txId" + 127 " AND start_time <= :mementoTime" + 128 " AND end_time > :mementoTime" + 129 " AND operation = :addOp"; 130 131 private static final String INSERT_MEMBERSHIP_IN_TX = 132 "INSERT INTO membership_tx_operations" + 133 " (subject_id, property, object_id, source_id, start_time, end_time, tx_id, operation)" + 134 " VALUES (:subjectId, :property, :targetId, :sourceId, :startTime, :endTime, :txId, :operation)"; 135 136 private static final String END_EXISTING_MEMBERSHIP = 137 "INSERT INTO membership_tx_operations" + 138 " (subject_id, property, object_id, source_id, start_time, end_time, tx_id, operation)" + 139 " SELECT m.subject_id, m.property, m.object_id, m.source_id, m.start_time, :endTime, :txId, :deleteOp" + 140 " FROM membership m" + 141 " WHERE m.source_id = :sourceId" + 142 " AND m.end_time = :noEndTime" + 143 " AND m.subject_id = :subjectId" + 144 " AND m.property = :property" + 145 " AND m.object_id = :objectId"; 146 147 private static final String CLEAR_ENTRY_IN_TX = 148 "DELETE FROM membership_tx_operations" + 149 " WHERE source_id = :sourceId" + 150 " AND tx_id = :txId" + 151 " AND subject_id = :subjectId" + 152 " AND property = :property" + 153 " AND object_id = :objectId" + 154 " AND operation = :operation" + 155 " AND force_flag IS NULL"; 156 157 private static final String CLEAR_ALL_ADDED_FOR_SOURCE_IN_TX = 158 "DELETE FROM membership_tx_operations" + 159 " WHERE source_id = :sourceId" + 160 " AND tx_id = :txId" + 161 " AND operation = :addOp"; 162 163 // Add "delete" entries for all existing membership from the given source, if not already deleted 164 private static final String END_EXISTING_FOR_SOURCE = 165 "INSERT INTO membership_tx_operations" + 166 " (subject_id, property, object_id, source_id, start_time, end_time, tx_id, operation)" + 167 " SELECT subject_id, property, object_id, source_id, start_time, :endTime, :txId, :deleteOp" + 168 " FROM membership m" + 169 " WHERE source_id = :sourceId" + 170 " AND end_time = :noEndTime" + 171 " AND NOT EXISTS (" + 172 " SELECT TRUE" + 173 " FROM membership_tx_operations mtx" + 174 " WHERE mtx.subject_id = m.subject_id" + 175 " AND mtx.property = m.property" + 176 " AND mtx.object_id = m.object_id" + 177 " AND mtx.source_id = m.source_id" + 178 " AND mtx.operation = :deleteOp" + 179 ")"; 180 181 private static final String DELETE_EXISTING_FOR_SOURCE_AFTER = 182 "INSERT INTO membership_tx_operations" + 183 " (subject_id, property, object_id, source_id, start_time, end_time, tx_id, operation, force_flag)" + 184 " SELECT subject_id, property, object_id, source_id, start_time, end_time, :txId, :deleteOp, :forceFlag" + 185 " FROM membership m" + 186 " WHERE m.source_id = :sourceId" + 187 " AND (m.start_time >= :startTime" + 188 " OR m.end_time >= :startTime)"; 189 190 private static final String PURGE_ALL_REFERENCES_MEMBERSHIP = 191 "DELETE from membership" + 192 " where source_id = :targetId" + 193 " OR subject_id = :targetId" + 194 " OR object_id = :targetId"; 195 196 private static final String PURGE_ALL_REFERENCES_TRANSACTION = 197 "DELETE from membership_tx_operations" + 198 " WHERE tx_id = :txId" + 199 " AND (source_id = :targetId" + 200 " OR subject_id = :targetId" + 201 " OR object_id = :targetId)"; 202 203 private static final String COMMIT_DELETES = 204 "DELETE from membership" + 205 " WHERE EXISTS (" + 206 " SELECT TRUE" + 207 " FROM membership_tx_operations mto" + 208 " WHERE mto.tx_id = :txId" + 209 " AND mto.operation = :deleteOp" + 210 " AND mto.force_flag = :forceFlag" + 211 " AND membership.source_id = mto.source_id" + 212 " AND membership.subject_id = mto.subject_id" + 213 " AND membership.property = mto.property" + 214 " AND membership.object_id = mto.object_id" + 215 " )"; 216 217 private static final String COMMIT_ENDS_H2 = 218 "UPDATE membership m" + 219 " SET end_time = (" + 220 " SELECT mto.end_time" + 221 " FROM membership_tx_operations mto" + 222 " WHERE mto.tx_id = :txId" + 223 " AND m.source_id = mto.source_id" + 224 " AND m.subject_id = mto.subject_id" + 225 " AND m.property = mto.property" + 226 " AND m.object_id = mto.object_id" + 227 " AND mto.operation = :deleteOp" + 228 " )" + 229 " WHERE EXISTS (" + 230 "SELECT TRUE" + 231 " FROM membership_tx_operations mto" + 232 " WHERE mto.tx_id = :txId" + 233 " AND mto.operation = :deleteOp" + 234 " AND m.source_id = mto.source_id" + 235 " AND m.subject_id = mto.subject_id" + 236 " AND m.property = mto.property" + 237 " AND m.object_id = mto.object_id" + 238 " )"; 239 240 private static final String COMMIT_ENDS_POSTGRES = 241 "UPDATE membership" + 242 " SET end_time = mto.end_time" + 243 " FROM membership_tx_operations mto" + 244 " WHERE mto.tx_id = :txId" + 245 " AND mto.operation = :deleteOp" + 246 " AND membership.source_id = mto.source_id" + 247 " AND membership.subject_id = mto.subject_id" + 248 " AND membership.property = mto.property" + 249 " AND membership.object_id = mto.object_id"; 250 251 private static final String COMMIT_ENDS_MYSQL = 252 "UPDATE membership m" + 253 " INNER JOIN membership_tx_operations mto ON" + 254 " m.source_id = mto.source_id" + 255 " AND m.subject_id = mto.subject_id" + 256 " AND m.subject_id = mto.subject_id" + 257 " AND m.property = mto.property" + 258 " AND m.object_id = mto.object_id" + 259 " SET m.end_time = mto.end_time" + 260 " WHERE mto.tx_id = :txId" + 261 " AND mto.operation = :deleteOp"; 262 263 private static final Map<DbPlatform, String> COMMIT_ENDS_MAP = Map.of( 264 DbPlatform.MYSQL, COMMIT_ENDS_MYSQL, 265 DbPlatform.MARIADB, COMMIT_ENDS_MYSQL, 266 DbPlatform.POSTGRESQL, COMMIT_ENDS_POSTGRES, 267 DbPlatform.H2, COMMIT_ENDS_H2 268 ); 269 270 // Transfer all "add" operations from tx to committed membership, unless the entry already exists 271 private static final String COMMIT_ADDS = 272 "INSERT INTO membership" + 273 " (subject_id, property, object_id, source_id, start_time, end_time)" + 274 " SELECT subject_id, property, object_id, source_id, start_time, end_time" + 275 " FROM membership_tx_operations mto" + 276 " WHERE mto.tx_id = :txId" + 277 " AND mto.operation = :addOp" + 278 " AND NOT EXISTS (" + 279 " SELECT TRUE" + 280 " FROM membership m" + 281 " WHERE m.source_id = mto.source_id" + 282 " AND m.subject_id = mto.subject_id" + 283 " AND m.property = mto.property" + 284 " AND m.object_id = mto.object_id" + 285 " AND m.start_time = mto.start_time" + 286 " AND m.end_time = mto.end_time" + 287 " )"; 288 289 private static final String DELETE_TRANSACTION = 290 "DELETE FROM membership_tx_operations" + 291 " WHERE tx_id = :txId"; 292 293 private static final String TRUNCATE_MEMBERSHIP = "TRUNCATE TABLE membership"; 294 295 private static final String TRUNCATE_MEMBERSHIP_TX = "TRUNCATE TABLE membership_tx_operations"; 296 297 @Inject 298 private DataSource dataSource; 299 300 private NamedParameterJdbcTemplate jdbcTemplate; 301 302 private DbPlatform dbPlatform; 303 304 private static final Map<DbPlatform, String> DDL_MAP = Map.of( 305 DbPlatform.MYSQL, "sql/mysql-membership.sql", 306 DbPlatform.H2, "sql/default-membership.sql", 307 DbPlatform.POSTGRESQL, "sql/default-membership.sql", 308 DbPlatform.MARIADB, "sql/mariadb-membership.sql" 309 ); 310 311 @PostConstruct 312 public void setUp() { 313 jdbcTemplate = new NamedParameterJdbcTemplate(getDataSource()); 314 315 dbPlatform = DbPlatform.fromDataSource(dataSource); 316 317 Preconditions.checkArgument(DDL_MAP.containsKey(dbPlatform), 318 "Missing DDL mapping for %s", dbPlatform); 319 320 final var ddl = DDL_MAP.get(dbPlatform); 321 log.debug("Applying ddl: {}", ddl); 322 DatabasePopulatorUtils.execute( 323 new ResourceDatabasePopulator(new DefaultResourceLoader().getResource("classpath:" + ddl)), 324 dataSource); 325 } 326 327 /** 328 * End a membership entry, setting an end time if committed, or clearing from the current tx 329 * if it was newly added. 330 * 331 * @param txId transaction id 332 * @param sourceId ID of the direct/indirect container whose membership should be ended 333 * @param membership membership triple to end 334 * @param endTime the time the resource was deleted, generally its last modified 335 */ 336 @Transactional 337 public void endMembership(final String txId, final FedoraId sourceId, final Triple membership, 338 final Instant endTime) { 339 final Map<String, Object> parameterSource = Map.of( 340 TX_ID_PARAM, txId, 341 SOURCE_ID_PARAM, sourceId.getFullId(), 342 SUBJECT_ID_PARAM, membership.getSubject().getURI(), 343 PROPERTY_PARAM, membership.getPredicate().getURI(), 344 OBJECT_ID_PARAM, membership.getObject().getURI(), 345 OPERATION_PARAM, ADD_OPERATION); 346 347 final int affected = jdbcTemplate.update(CLEAR_ENTRY_IN_TX, parameterSource); 348 349 // If no rows were deleted, then assume we need to delete permanent entry 350 if (affected == 0) { 351 final Map<String, Object> parameterSource2 = Map.of( 352 TX_ID_PARAM, txId, 353 SOURCE_ID_PARAM, sourceId.getFullId(), 354 SUBJECT_ID_PARAM, membership.getSubject().getURI(), 355 PROPERTY_PARAM, membership.getPredicate().getURI(), 356 OBJECT_ID_PARAM, membership.getObject().getURI(), 357 END_TIME_PARAM, formatInstant(endTime), 358 NO_END_TIME_PARAM, NO_END_TIMESTAMP, 359 DELETE_OP_PARAM, DELETE_OPERATION); 360 jdbcTemplate.update(END_EXISTING_MEMBERSHIP, parameterSource2); 361 } 362 } 363 364 /** 365 * End all membership properties resulting from the specified source container 366 * @param txId transaction id 367 * @param sourceId ID of the direct/indirect container whose membership should be ended 368 * @param endTime the time the resource was deleted, generally its last modified 369 */ 370 @Transactional 371 public void endMembershipForSource(final String txId, final FedoraId sourceId, final Instant endTime) { 372 final Map<String, Object> parameterSource = Map.of( 373 TX_ID_PARAM, txId, 374 SOURCE_ID_PARAM, sourceId.getFullId(), 375 ADD_OP_PARAM, ADD_OPERATION); 376 377 jdbcTemplate.update(CLEAR_ALL_ADDED_FOR_SOURCE_IN_TX, parameterSource); 378 379 final Map<String, Object> parameterSource2 = Map.of( 380 TX_ID_PARAM, txId, 381 SOURCE_ID_PARAM, sourceId.getFullId(), 382 END_TIME_PARAM, formatInstant(endTime), 383 NO_END_TIME_PARAM, NO_END_TIMESTAMP, 384 DELETE_OP_PARAM, DELETE_OPERATION); 385 jdbcTemplate.update(END_EXISTING_FOR_SOURCE, parameterSource2); 386 } 387 388 /** 389 * Delete membership entries that are active at or after the given timestamp for the specified source 390 * @param txId transaction id 391 * @param sourceId ID of the direct/indirect container 392 * @param afterTime time at or after which membership should be removed 393 */ 394 @Transactional 395 public void deleteMembershipForSourceAfter(final String txId, final FedoraId sourceId, final Instant afterTime) { 396 // Clear all membership added in this transaction 397 final Map<String, Object> parameterSource = Map.of( 398 TX_ID_PARAM, txId, 399 SOURCE_ID_PARAM, sourceId.getFullId(), 400 ADD_OP_PARAM, ADD_OPERATION); 401 402 jdbcTemplate.update(CLEAR_ALL_ADDED_FOR_SOURCE_IN_TX, parameterSource); 403 404 final var afterTimestamp = afterTime == null ? NO_START_TIMESTAMP : formatInstant(afterTime); 405 406 // Delete all existing membership entries that start after or end after the given timestamp 407 final Map<String, Object> parameterSource2 = Map.of( 408 TX_ID_PARAM, txId, 409 SOURCE_ID_PARAM, sourceId.getFullId(), 410 START_TIME_PARAM, afterTimestamp, 411 FORCE_PARAM, FORCE_FLAG, 412 DELETE_OP_PARAM, DELETE_OPERATION); 413 jdbcTemplate.update(DELETE_EXISTING_FOR_SOURCE_AFTER, parameterSource2); 414 } 415 416 /** 417 * Clean up any references to the target id, in transactions and outside 418 * @param txId transaction id 419 * @param targetId identifier of the resource to cleanup membership references for 420 */ 421 @Transactional 422 public void deleteMembershipReferences(final String txId, final FedoraId targetId) { 423 final Map<String, Object> parameterSource = Map.of( 424 TARGET_ID_PARAM, targetId.getFullId(), 425 TX_ID_PARAM, txId); 426 427 jdbcTemplate.update(PURGE_ALL_REFERENCES_TRANSACTION, parameterSource); 428 jdbcTemplate.update(PURGE_ALL_REFERENCES_MEMBERSHIP, parameterSource); 429 } 430 431 /** 432 * Add new membership property to the index, clearing any delete 433 * operations for the property if necessary. 434 * @param txId transaction id 435 * @param sourceId ID of the direct/indirect container which produced the membership 436 * @param membership membership triple 437 * @param startTime time the membership triple was added 438 */ 439 @Transactional 440 public void addMembership(final String txId, final FedoraId sourceId, final Triple membership, 441 final Instant startTime) { 442 // Clear any existing delete operation for this membership 443 final Map<String, Object> parametersDelete = Map.of( 444 TX_ID_PARAM, txId, 445 SOURCE_ID_PARAM, sourceId.getFullId(), 446 SUBJECT_ID_PARAM, membership.getSubject().getURI(), 447 PROPERTY_PARAM, membership.getPredicate().getURI(), 448 OBJECT_ID_PARAM, membership.getObject().getURI(), 449 OPERATION_PARAM, DELETE_OPERATION); 450 451 jdbcTemplate.update(CLEAR_ENTRY_IN_TX, parametersDelete); 452 453 // Add the new membership operation 454 addMembership(txId, sourceId, membership, startTime, null); 455 } 456 457 /** 458 * Add new membership property to the index 459 * @param txId transaction id 460 * @param sourceId ID of the direct/indirect container which produced the membership 461 * @param membership membership triple 462 * @param startTime time the membership triple was added 463 * @param endTime time the membership triple ends, or never if not provided 464 */ 465 public void addMembership(final String txId, final FedoraId sourceId, final Triple membership, 466 final Instant startTime, final Instant endTime) { 467 final var endTimestamp = endTime == null ? NO_END_TIMESTAMP : formatInstant(endTime); 468 // Add the new membership operation 469 final Map<String, Object> parameterSource = Map.of( 470 SUBJECT_ID_PARAM, membership.getSubject().getURI(), 471 PROPERTY_PARAM, membership.getPredicate().getURI(), 472 TARGET_ID_PARAM, membership.getObject().getURI(), 473 SOURCE_ID_PARAM, sourceId.getFullId(), 474 START_TIME_PARAM, formatInstant(startTime), 475 END_TIME_PARAM, endTimestamp, 476 TX_ID_PARAM, txId, 477 OPERATION_PARAM, ADD_OPERATION); 478 479 jdbcTemplate.update(INSERT_MEMBERSHIP_IN_TX, parameterSource); 480 } 481 482 /** 483 * Get a stream of membership triples with 484 * @param txId transaction from which membership will be retrieved, or null for no transaction 485 * @param subjectId ID of the subject 486 * @return Stream of membership triples 487 */ 488 public Stream<Triple> getMembership(final String txId, final FedoraId subjectId) { 489 final Node subjectNode = NodeFactory.createURI(subjectId.getBaseId()); 490 491 final RowMapper<Triple> membershipMapper = (rs, rowNum) -> 492 Triple.create(subjectNode, 493 NodeFactory.createURI(rs.getString("property")), 494 NodeFactory.createURI(rs.getString("object_id"))); 495 496 List<Triple> membership = null; 497 if (subjectId.isMemento()) { 498 final Map<String, Object> parameterSource = Map.of( 499 SUBJECT_ID_PARAM, subjectId.getBaseId(), 500 MEMENTO_TIME_PARAM, formatInstant(subjectId.getMementoInstant()), 501 TX_ID_PARAM, txId, 502 ADD_OP_PARAM, ADD_OPERATION, 503 DELETE_OP_PARAM, DELETE_OPERATION); 504 505 membership = jdbcTemplate.query(SELECT_MEMBERSHIP_MEMENTO_IN_TX, parameterSource, membershipMapper); 506 } else { 507 final Map<String, Object> parameterSource = Map.of( 508 SUBJECT_ID_PARAM, subjectId.getFullId(), 509 NO_END_TIME_PARAM, NO_END_TIMESTAMP, 510 TX_ID_PARAM, txId, 511 ADD_OP_PARAM, ADD_OPERATION, 512 DELETE_OP_PARAM, DELETE_OPERATION); 513 514 membership = jdbcTemplate.query(SELECT_MEMBERSHIP_IN_TX, parameterSource, membershipMapper); 515 } 516 517 return membership.stream(); 518 } 519 520 /** 521 * Perform a commit of operations stored in the specified transaction 522 * @param txId transaction id 523 */ 524 @Transactional 525 public void commitTransaction(final String txId) { 526 final Map<String, String> parameterSource = Map.of(TX_ID_PARAM, txId, 527 ADD_OP_PARAM, ADD_OPERATION, 528 DELETE_OP_PARAM, DELETE_OPERATION, 529 FORCE_PARAM, FORCE_FLAG); 530 531 jdbcTemplate.update(COMMIT_DELETES, parameterSource); 532 final int ends = jdbcTemplate.update(COMMIT_ENDS_MAP.get(this.dbPlatform), parameterSource); 533 final int adds = jdbcTemplate.update(COMMIT_ADDS, parameterSource); 534 final int cleaned = jdbcTemplate.update(DELETE_TRANSACTION, parameterSource); 535 536 log.debug("Completed commit, {} ended, {} adds, {} operations", ends, adds, cleaned); 537 } 538 539 /** 540 * Delete all entries related to a transaction 541 * @param txId transaction id 542 */ 543 public void deleteTransaction(final String txId) { 544 final Map<String, String> parameterSource = Map.of(TX_ID_PARAM, txId); 545 jdbcTemplate.update(DELETE_TRANSACTION, parameterSource); 546 } 547 548 /** 549 * Format an instant to a timestamp without milliseconds, due to precision 550 * issues with memento datetimes. 551 * @param instant 552 * @return 553 */ 554 private Timestamp formatInstant(final Instant instant) { 555 final var timestamp = Timestamp.from(instant); 556 timestamp.setNanos(0); 557 return timestamp; 558 } 559 560 /** 561 * Clear all entries from the index 562 */ 563 @Transactional 564 public void clearIndex() { 565 jdbcTemplate.update(TRUNCATE_MEMBERSHIP, Map.of()); 566 jdbcTemplate.update(TRUNCATE_MEMBERSHIP_TX, Map.of()); 567 } 568 569 /** 570 * Log all membership entries, for debugging usage only 571 */ 572 public void logMembership() { 573 log.info("source_id, subject_id, property, object_id, start_time, end_time"); 574 jdbcTemplate.query(SELECT_ALL_MEMBERSHIP, new RowCallbackHandler() { 575 @Override 576 public void processRow(final ResultSet rs) throws SQLException { 577 log.info("{}, {}, {}, {}, {}, {}", rs.getString("source_id"), rs.getString("subject_id"), 578 rs.getString("property"), rs.getString("object_id"), rs.getTimestamp("start_time"), 579 rs.getTimestamp("end_time")); 580 } 581 }); 582 } 583 584 /** 585 * Log all membership operations, for debugging usage only 586 */ 587 public void logOperations() { 588 log.info("source_id, subject_id, property, object_id, start_time, end_time, tx_id, operation, force_flag"); 589 jdbcTemplate.query(SELECT_ALL_OPERATIONS, new RowCallbackHandler() { 590 @Override 591 public void processRow(final ResultSet rs) throws SQLException { 592 log.info("{}, {}, {}, {}, {}, {}, {}, {}, {}", 593 rs.getString("source_id"), rs.getString("subject_id"), rs.getString("property"), 594 rs.getString("object_id"), rs.getTimestamp("start_time"), rs.getTimestamp("end_time"), 595 rs.getString("tx_id"), rs.getString("operation"), rs.getString("force_flag")); 596 } 597 }); 598 } 599 600 /** 601 * Set the JDBC datastore. 602 * @param dataSource the dataStore. 603 */ 604 public void setDataSource(final DataSource dataSource) { 605 this.dataSource = dataSource; 606 } 607 608 /** 609 * Get the JDBC datastore. 610 * @return the dataStore. 611 */ 612 public DataSource getDataSource() { 613 return dataSource; 614 } 615}