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.tasks.api;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.jpa.migrate.MigrationTaskList;
024import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
025import com.google.common.collect.Multimap;
026import com.google.common.collect.MultimapBuilder;
027import jakarta.annotation.Nonnull;
028import org.apache.commons.lang3.EnumUtils;
029import org.apache.commons.lang3.Validate;
030import org.flywaydb.core.api.MigrationVersion;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import java.util.Collection;
035
036import static java.util.Objects.nonNull;
037
038public class BaseMigrationTasks<T extends Enum> {
039        private static final Logger ourLog = LoggerFactory.getLogger(BaseMigrationTasks.class);
040        private Multimap<T, BaseTask> myTasks =
041                        MultimapBuilder.hashKeys().arrayListValues().build();
042
043        @SuppressWarnings("unchecked")
044        public MigrationTaskList getTaskList(@Nonnull T theFrom, @Nonnull T theTo) {
045                Validate.notNull(theFrom);
046                Validate.notNull(theTo);
047                Validate.isTrue(theFrom.ordinal() < theTo.ordinal(), "From version must be lower than to version");
048
049                MigrationTaskList retVal = new MigrationTaskList();
050                for (Object nextVersion : EnumUtils.getEnumList(theFrom.getClass())) {
051                        if (((T) nextVersion).ordinal() <= theFrom.ordinal()) {
052                                continue;
053                        }
054                        if (((T) nextVersion).ordinal() > theTo.ordinal()) {
055                                continue;
056                        }
057
058                        Collection<BaseTask> nextValues = myTasks.get((T) nextVersion);
059                        retVal.addAll(nextValues);
060                }
061
062                return retVal;
063        }
064
065        public Builder forVersion(T theRelease) {
066                IAcceptsTasks sink = theTask -> {
067                        theTask.validate();
068                        myTasks.put(theRelease, theTask);
069                };
070                return new Builder(toReleaseName(theRelease), sink);
071        }
072
073        @Nonnull
074        protected String toReleaseName(T theRelease) {
075                return theRelease.name();
076        }
077
078        public MigrationTaskList getAllTasks(T... theVersionEnumValues) {
079                MigrationTaskList retval = new MigrationTaskList();
080                for (T nextVersion : theVersionEnumValues) {
081                        Collection<BaseTask> nextValues = myTasks.get(nextVersion);
082                        ourLog.debug(
083                                        "Version {} has {} migration tasks", nextVersion, nextValues != null ? nextValues.size() : "(no)");
084                        if (nextValues != null && !nextValues.isEmpty()) {
085                                validate(nextValues);
086                                retval.addAll(nextValues);
087                        }
088                }
089                return retval;
090        }
091
092        public boolean hasTasksForVersion(T theRelease) {
093                Collection<BaseTask> baseTasks = myTasks.get(theRelease);
094                return nonNull(baseTasks) && !baseTasks.isEmpty();
095        }
096
097        protected BaseTask getTaskWithVersion(String theMigrationVersion) {
098                // First normalize the version number
099                String expectedVersion =
100                                MigrationVersion.fromVersion(theMigrationVersion).getVersion();
101
102                return myTasks.values().stream()
103                                .filter(task -> expectedVersion.equals(task.getMigrationVersion()))
104                                .findFirst()
105                                .get();
106        }
107
108        void validate(Collection<BaseTask> theTasks) {
109                MigrationVersion lastVersion = null;
110                for (BaseTask task : theTasks) {
111                        task.validateVersion();
112                        String version = task.getMigrationVersion();
113                        MigrationVersion migrationVersion = MigrationVersion.fromVersion(version);
114                        if (lastVersion != null) {
115                                if (migrationVersion.compareTo(lastVersion) <= 0) {
116                                        throw new IllegalStateException(
117                                                        Msg.code(51) + "Migration version " + migrationVersion + " found after migration version "
118                                                                        + lastVersion + ".  Migrations need to be in order by version number.");
119                                }
120                        }
121                        lastVersion = migrationVersion;
122                }
123        }
124
125        public interface IAcceptsTasks {
126                void addTask(BaseTask theTask);
127        }
128}