/* * 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.testing; import java.io.Closeable; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigRenderOptions; public class TestMetastoreDatabaseFactory { private static final Object syncObject = new Object(); private static TestMetastoreDatabaseServer testMetastoreDatabaseServer; private static Set<ITestMetastoreDatabase> instances = new HashSet<>(); private TestMetastoreDatabaseFactory() { } public static Config getDefaultConfig() { return ConfigFactory.defaultOverrides().withFallback(ConfigFactory.load()); } public static ITestMetastoreDatabase get() throws Exception { return TestMetastoreDatabaseFactory.get("latest"); } public static ITestMetastoreDatabase get(String version) throws Exception { return get(version, getDefaultConfig()); } public static ITestMetastoreDatabase get(String version, Config dbConfig) throws Exception { try { synchronized (syncObject) { ensureDatabaseExists(dbConfig); TestMetadataDatabase instance = new TestMetadataDatabase(testMetastoreDatabaseServer, version); instances.add(instance); return instance; } } catch (Exception e) { throw new RuntimeException("Failed to create TestMetastoreDatabase with version " + version + " and config " + dbConfig.root().render(ConfigRenderOptions.defaults().setFormatted(true).setJson(true)) + " cause: " + e, e); } } static void release(ITestMetastoreDatabase instance) throws IOException { synchronized (syncObject) { if (instances.remove(instance) && instances.size() == 0) { testMetastoreDatabaseServer.close(); testMetastoreDatabaseServer = null; } } } private static void ensureDatabaseExists(Config dbConfig) throws Exception { if (testMetastoreDatabaseServer == null) { try (Mutex ignored = new Mutex()) { if (testMetastoreDatabaseServer == null) { testMetastoreDatabaseServer = new TestMetastoreDatabaseServer(dbConfig); } } } } private static class Mutex implements Closeable { private final Object syncObject = new Object(); private final AtomicBoolean isLocked = new AtomicBoolean(false); private FileChannel fileChannel; private FileLock fileLock; public Mutex() throws IOException { take(); } @Override public void close() { release(); } private boolean take() throws IOException { if (!isLocked.get()) { synchronized (syncObject) { if (!isLocked.get()) { if (fileChannel == null) { Path lockPath = Paths.get(System.getProperty("user.home")).resolve(".embedmysql.lock"); fileChannel = FileChannel.open(lockPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ); } fileLock = fileChannel.lock(); isLocked.set(true); return true; } return true; } } return true; } private boolean release() { if (isLocked.get()) { synchronized (syncObject) { if (isLocked.get()) { if (fileLock != null) { boolean result = true; try { fileLock.close(); fileLock = null; isLocked.set(false); } catch (IOException ignored) { result = false; } if (fileChannel != null) { try { fileChannel.close(); } catch (IOException ignored) { result = false; } } return result; } isLocked.set(false); return true; } return true; } } return true; } } }