/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.jpa; import com.google.common.collect.ImmutableMap; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Resource; import org.apereo.portal.jpa.BasePortalJpaDao.PortalTransactional; import org.apereo.portal.version.VersionUtils; import org.apereo.portal.version.dao.VersionDao; import org.apereo.portal.version.om.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("versionedDataUpdater") public class VersionedDataUpdaterImpl implements VersionedDataUpdater { protected final Logger logger = LoggerFactory.getLogger(getClass()); private Map<String, Version> requiredProductVersions = Collections.emptyMap(); private VersionDao versionDao; private Map<String, SortedSet<IVersionedDatabaseUpdateHelper>> databaseUpdateHelpers = Collections.emptyMap(); @Resource(name = "productVersions") public void setRequiredProductVersions(Map<String, Version> requiredProductVersions) { this.requiredProductVersions = ImmutableMap.copyOf(requiredProductVersions); } @Autowired public void setVersionedDatabaseUpdateHelpers( Collection<IVersionedDatabaseUpdateHelper> helpers) { this.databaseUpdateHelpers = new HashMap<String, SortedSet<IVersionedDatabaseUpdateHelper>>(); for (final IVersionedDatabaseUpdateHelper helper : helpers) { final String databaseName = helper.getDatabaseName(); SortedSet<IVersionedDatabaseUpdateHelper> updaters = this.databaseUpdateHelpers.get(databaseName); if (updaters == null) { updaters = new TreeSet<IVersionedDatabaseUpdateHelper>( VersionedDatabaseUpdateHelperComparator.INSTANCE); this.databaseUpdateHelpers.put(databaseName, updaters); } updaters.add(helper); } } @Autowired public void setVersionDao(VersionDao versionDao) { this.versionDao = versionDao; } @Override @PortalTransactional public void postInitDatabase(String product) { final Version version = this.requiredProductVersions.get(product); if (version == null) { throw new IllegalArgumentException("No Version is configured for: " + product); } logger.info("PostInit - Set {} version to {}", product, version); this.versionDao.setVersion(product, version); } @Override @OpenEntityManager(unitName = BasePortalJpaDao.PERSISTENCE_UNIT_NAME) public void preUpdateDatabase(String product) { //This happens first because even if there are no updaters we need to make sure the attempted update is valid final Version dbVersion = getAndVerifyDatabaseVersionForUpdate(product); final SortedSet<IVersionedDatabaseUpdateHelper> updateHelpers = this.databaseUpdateHelpers.get(product); if (updateHelpers == null || updateHelpers.isEmpty()) { logger.info( "No IVersionedDatabaseUpdateHelpers configured for database {}, nothing will be done in preUpdate", product); } else { //updateHelpers is sorted oldest to newest by version so iterate through and run the updaters that apply for (final IVersionedDatabaseUpdateHelper updateHelper : updateHelpers) { final Version updateVersion = updateHelper.getVersion(); if (dbVersion.equals(updateVersion) || dbVersion.isBefore(updateVersion)) { logger.info("PreUpdate {} from {} to {}", product, dbVersion, updateVersion); updateHelper.preUpdate(); logger.info( "PreUpdate {} from {} to {} complete", product, dbVersion, updateVersion); } } } } @Override @PortalTransactional public void postUpdateDatabase(String product) { //This happens first because even if there are no updaters we need to make sure the attempted update is valid final Version dbVersion = getAndVerifyDatabaseVersionForUpdate(product); final SortedSet<IVersionedDatabaseUpdateHelper> updateHelpers = this.databaseUpdateHelpers.get(product); if (updateHelpers == null || updateHelpers.isEmpty()) { logger.info( "No IVersionedDatabaseUpdateHelpers configured for database {}, nothing will be done in postUpdate", product); } else { //updateHelpers is sorted oldest to newest by version so iterate through and run the updaters that apply for (final IVersionedDatabaseUpdateHelper updateHelper : updateHelpers) { final Version updateVersion = updateHelper.getVersion(); if (dbVersion.equals(updateVersion) || dbVersion.isBefore(updateVersion)) { logger.info("PostUpdate {} from {} to {}", product, dbVersion, updateVersion); updateHelper.postUpdate(); logger.info( "PostUpdate {} from {} to {} complete", product, dbVersion, updateVersion); } } } //Update the db version number final Version codeVersion = this.requiredProductVersions.get(product); logger.info("PostUpdate - Set {} version to {}", product, codeVersion); this.versionDao.setVersion(product, codeVersion); } protected Version getAndVerifyDatabaseVersionForUpdate(String product) { final Version codeVersion = this.requiredProductVersions.get(product); if (codeVersion == null) { throw new IllegalStateException("No code version configured for " + product); } Version dbVersion = this.versionDao.getVersion(product); if (dbVersion == null) { //If null assume version 4.0.0 dbVersion = VersionUtils.parseVersion("4.0.0"); } if (!VersionUtils.canUpdate(dbVersion, codeVersion)) { throw new IllegalStateException( "Cannot update " + product + " db from " + dbVersion + " to " + codeVersion); } if (codeVersion.equals(dbVersion)) { logger.info("{} code version matches database version. No update needed.", product); } return dbVersion; } private static final class VersionedDatabaseUpdateHelperComparator implements Comparator<IVersionedDatabaseUpdateHelper> { public static final VersionedDatabaseUpdateHelperComparator INSTANCE = new VersionedDatabaseUpdateHelperComparator(); @Override public int compare(IVersionedDatabaseUpdateHelper o1, IVersionedDatabaseUpdateHelper o2) { if (o1 == o2) { return 0; } if (o1 == null) { return -1; } if (o2 == null) { return 1; } return o1.getVersion().compareTo(o2.getVersion()); } } }