/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 gobblin.metastore; import javax.sql.DataSource; import java.io.IOException; import java.net.URL; import java.util.Collection; import java.util.List; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.FlywayException; import org.flywaydb.core.api.MigrationInfoService; import org.flywaydb.core.api.MigrationVersion; import org.reflections.Reflections; import org.reflections.util.ClasspathHelper; import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.inject.Inject; import gobblin.metastore.database.SupportedDatabaseVersion; import gobblin.metastore.database.VersionedDatabaseJobHistoryStore; import gobblin.rest.JobExecutionInfo; import gobblin.rest.JobExecutionQuery; import org.reflections.util.ClasspathHelper; /** * An implementation of {@link JobHistoryStore} backed by MySQL. * * <p> * The DDLs for the MySQL job history store can be found under metastore/src/main/resources. * </p> * * @author Yinan Li */ public class DatabaseJobHistoryStore implements JobHistoryStore { private final VersionedDatabaseJobHistoryStore versionedStore; @Inject public DatabaseJobHistoryStore(DataSource dataSource) throws InstantiationException, IllegalAccessException, ClassNotFoundException { MigrationVersion databaseVersion = getDatabaseVersion(dataSource); this.versionedStore = findVersionedDatabaseJobHistoryStore(databaseVersion); this.versionedStore.init(dataSource); } @Override public synchronized void put(JobExecutionInfo jobExecutionInfo) throws IOException { this.versionedStore.put(jobExecutionInfo); } @Override public synchronized List<JobExecutionInfo> get(JobExecutionQuery query) throws IOException { return this.versionedStore.get(query); } @Override public void close() throws IOException { this.versionedStore.close(); } private static MigrationVersion getDatabaseVersion(DataSource dataSource) throws FlywayException { Flyway flyway = new Flyway(); flyway.setDataSource(dataSource); MigrationInfoService info = flyway.info(); MigrationVersion currentVersion = MigrationVersion.EMPTY; if (info.current() != null) { currentVersion = info.current().getVersion(); } return currentVersion; } private static Collection<URL> effectiveClassPathUrls(ClassLoader... classLoaders) { return ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoaders)); } private static VersionedDatabaseJobHistoryStore findVersionedDatabaseJobHistoryStore(MigrationVersion requiredVersion) throws IllegalAccessException, InstantiationException, ClassNotFoundException { Class<?> foundClazz = null; Class<?> defaultClazz = null; MigrationVersion defaultVersion = MigrationVersion.EMPTY; // Scan all packages Reflections reflections = new Reflections("gobblin.metastore.database", effectiveClassPathUrls(DatabaseJobHistoryStore.class.getClassLoader())); for (Class<?> clazz : Sets.intersection(reflections.getTypesAnnotatedWith(SupportedDatabaseVersion.class), reflections.getSubTypesOf(VersionedDatabaseJobHistoryStore.class))) { SupportedDatabaseVersion annotation = clazz.getAnnotation(SupportedDatabaseVersion.class); String version = annotation.version(); MigrationVersion actualVersion = MigrationVersion.fromVersion(Strings.isNullOrEmpty(version) ? null : version); if (annotation.isDefault() && actualVersion.compareTo(defaultVersion) > 0) { defaultClazz = clazz; defaultVersion = actualVersion; } if (actualVersion.compareTo(requiredVersion) == 0) { foundClazz = clazz; } } if (foundClazz == null) { foundClazz = defaultClazz; } if (foundClazz == null) { throw new ClassNotFoundException( String.format("Could not find an instance of %s which supports database " + "version %s.", VersionedDatabaseJobHistoryStore.class.getSimpleName(), requiredVersion.toString())); } return (VersionedDatabaseJobHistoryStore) foundClazz.newInstance(); } }