001/*- 002 * #%L 003 * HAPI FHIR Server - SQL Migration 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.migrate; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.migrate.dao.HapiMigrationDao; 024import ca.uhn.fhir.jpa.migrate.entity.HapiMigrationEntity; 025import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; 026import org.flywaydb.core.api.MigrationVersion; 027 028import java.util.Optional; 029import java.util.Set; 030 031public class HapiMigrationStorageSvc { 032 public static final String UNKNOWN_VERSION = "unknown"; 033 public static final String LOCK_TYPE = "hapi-fhir-lock"; 034 035 private final HapiMigrationDao myHapiMigrationDao; 036 037 public HapiMigrationStorageSvc(HapiMigrationDao theHapiMigrationDao) { 038 myHapiMigrationDao = theHapiMigrationDao; 039 } 040 041 public String getMigrationTablename() { 042 return myHapiMigrationDao.getMigrationTablename(); 043 } 044 045 /** 046 * Returns a list of migration tasks that have not yet been successfully run against the database 047 * @param theTaskList the full list of tasks for this release 048 * @return a list of tasks that have not yet been successfully run against the database 049 */ 050 public MigrationTaskList diff(MigrationTaskList theTaskList) { 051 Set<MigrationVersion> appliedMigrationVersions = fetchAppliedMigrationVersions(); 052 053 return theTaskList.diff(appliedMigrationVersions); 054 } 055 056 /** 057 * 058 * @return a list of migration versions that have been successfully run against the database 059 */ 060 Set<MigrationVersion> fetchAppliedMigrationVersions() { 061 return myHapiMigrationDao.fetchSuccessfulMigrationVersions(); 062 } 063 064 /** 065 * 066 * @return the most recent version that was run against the database (used for logging purposes) 067 */ 068 public String getLatestAppliedVersion() { 069 return fetchAppliedMigrationVersions().stream() 070 .sorted() 071 .map(MigrationVersion::toString) 072 .reduce((first, second) -> second) 073 .orElse(UNKNOWN_VERSION); 074 } 075 076 /** 077 * Save a migration task to the database 078 */ 079 public void saveTask(BaseTask theBaseTask, Integer theMillis, boolean theSuccess) { 080 HapiMigrationEntity entity = HapiMigrationEntity.fromBaseTask(theBaseTask); 081 entity.setExecutionTime(theMillis); 082 entity.setSuccess(theSuccess); 083 if (theBaseTask.getExecutionResult() != null) { 084 entity.setResult(theBaseTask.getExecutionResult().name()); 085 } 086 myHapiMigrationDao.save(entity); 087 } 088 089 /** 090 * Create the migration table if it does not already exist 091 */ 092 public boolean createMigrationTableIfRequired() { 093 return myHapiMigrationDao.createMigrationTableIfRequired(); 094 } 095 096 /** 097 * 098 * @param theLockDescription value of the Description for the lock record 099 * @return true if the record was successfully deleted 100 */ 101 public boolean deleteLockRecord(String theLockDescription) { 102 verifyNoOtherLocksPresent(theLockDescription); 103 104 // Remove the locking row 105 return myHapiMigrationDao.deleteLockRecord(HapiMigrationLock.LOCK_PID, theLockDescription); 106 } 107 108 void verifyNoOtherLocksPresent(String theLockDescription) { 109 Optional<HapiMigrationEntity> otherLockFound = 110 myHapiMigrationDao.findFirstByPidAndNotDescription(HapiMigrationLock.LOCK_PID, theLockDescription); 111 112 // Check that there are no other locks in place. This should not happen! 113 if (otherLockFound.isPresent()) { 114 throw new HapiMigrationException( 115 Msg.code(2152) + "Internal error: on unlocking, a competing lock was found"); 116 } 117 } 118 119 public boolean insertLockRecord(String theLockDescription) { 120 HapiMigrationEntity entity = new HapiMigrationEntity(); 121 entity.setPid(HapiMigrationLock.LOCK_PID); 122 entity.setType(LOCK_TYPE); 123 entity.setDescription(theLockDescription); 124 entity.setExecutionTime(0); 125 entity.setSuccess(true); 126 127 return myHapiMigrationDao.save(entity); 128 } 129 130 public Optional<HapiMigrationEntity> findFirstByPidAndNotDescription( 131 Integer theLockPid, String theLockDescription) { 132 return myHapiMigrationDao.findFirstByPidAndNotDescription(theLockPid, theLockDescription); 133 } 134}