diff --git a/extension/persistence/eclipselink/build.gradle b/extension/persistence/eclipselink/build.gradle index 91fb0c9c73..27b0bee4cd 100644 --- a/extension/persistence/eclipselink/build.gradle +++ b/extension/persistence/eclipselink/build.gradle @@ -24,4 +24,12 @@ dependencies { testImplementation(libs.h2) testImplementation(testFixtures(project(":polaris-core"))) + + sourceSets { + test { + resources { + srcDir 'src/test/data' + } + } + } } diff --git a/extension/persistence/eclipselink/src/main/java/io/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java b/extension/persistence/eclipselink/src/main/java/io/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java index e1f7bec833..a49b6071fb 100644 --- a/extension/persistence/eclipselink/src/main/java/io/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java +++ b/extension/persistence/eclipselink/src/main/java/io/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java @@ -19,7 +19,6 @@ import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL; import com.google.common.base.Predicates; -import com.google.common.collect.Maps; import io.polaris.core.PolarisCallContext; import io.polaris.core.context.RealmContext; import io.polaris.core.entity.PolarisBaseEntity; @@ -47,7 +46,11 @@ import jakarta.persistence.EntityTransaction; import jakarta.persistence.OptimisticLockException; import jakarta.persistence.Persistence; +import java.io.File; +import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,6 +70,7 @@ import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; /** * EclipseLink implementation of a Polaris metadata store supporting persisting and retrieving all @@ -80,7 +84,6 @@ public class PolarisEclipseLinkMetaStoreSessionImpl implements PolarisMetaStoreS private final ThreadLocal localSession = new ThreadLocal<>(); private final PolarisEclipseLinkStore store; private final PolarisStorageIntegrationProvider storageIntegrationProvider; - private static volatile Map properties; /** * Create a meta store session against provided realm. Each realm has its own database. @@ -97,19 +100,8 @@ public PolarisEclipseLinkMetaStoreSessionImpl( @NotNull RealmContext realmContext, @Nullable String confFile, @Nullable String persistenceUnitName) { - persistenceUnitName = persistenceUnitName == null ? "polaris" : persistenceUnitName; - Map properties = - loadProperties( - confFile == null ? "META-INF/persistence.xml" : confFile, persistenceUnitName); - // Replace database name in JDBC URL with realm - if (properties.containsKey(JDBC_URL)) { - properties.put( - JDBC_URL, properties.get(JDBC_URL).replace("{realm}", realmContext.getRealmIdentifier())); - } - properties.put(ECLIPSELINK_PERSISTENCE_XML, confFile); - - emf = Persistence.createEntityManagerFactory(persistenceUnitName, properties); + emf = createEntityManagerFactory(realmContext, confFile, persistenceUnitName); LOG.debug("Create EclipseLink Meta Store Session for {}", realmContext.getRealmIdentifier()); // init store @@ -118,13 +110,55 @@ public PolarisEclipseLinkMetaStoreSessionImpl( } /** Load the persistence unit properties from a given configuration file */ - private Map loadProperties(String confFile, String persistenceUnitName) { - if (properties != null) { - return properties; + private EntityManagerFactory createEntityManagerFactory( + @NotNull RealmContext realmContext, + @Nullable String confFile, + @Nullable String persistenceUnitName) { + ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader(); + try { + persistenceUnitName = persistenceUnitName == null ? "polaris" : persistenceUnitName; + confFile = confFile == null ? "META-INF/persistence.xml" : confFile; + + // Currently eclipseLink can only support configuration as a resource inside a jar. To support + // external configuration, persistence.xml needs be placed inside a jar and here is to add the + // jar to the classpath. + // Supported configuration file: META-INFO/persistence.xml, /tmp/conf.jar!/persistence.xml + int splitPosition = confFile.indexOf("!/"); + if (splitPosition != -1) { + String jarPrefixPath = confFile.substring(0, splitPosition); + confFile = confFile.substring(splitPosition + 2); + URL prefixUrl = this.getClass().getClassLoader().getResource(jarPrefixPath); + if (prefixUrl == null) { + prefixUrl = new File(jarPrefixPath).toURI().toURL(); + } + URLClassLoader currentClassLoader = + new URLClassLoader(new URL[] {prefixUrl}, this.getClass().getClassLoader()); + Thread.currentThread().setContextClassLoader(currentClassLoader); + } + + Map properties = loadProperties(confFile, persistenceUnitName); + // Replace database name in JDBC URL with realm + if (properties.containsKey(JDBC_URL)) { + properties.put( + JDBC_URL, + properties.get(JDBC_URL).replace("{realm}", realmContext.getRealmIdentifier())); + } + properties.put(ECLIPSELINK_PERSISTENCE_XML, confFile); + + return Persistence.createEntityManagerFactory(persistenceUnitName, properties); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + Thread.currentThread().setContextClassLoader(prevClassLoader); } + } + /** Load the persistence unit properties from a given configuration file */ + private Map loadProperties( + @NotNull String confFile, @NotNull String persistenceUnitName) throws Exception { try { - InputStream input = this.getClass().getClassLoader().getResourceAsStream(confFile); + InputStream input = + Thread.currentThread().getContextClassLoader().getResourceAsStream(confFile); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(input); @@ -141,16 +175,15 @@ private Map loadProperties(String confFile, String persistenceUn nodeMap.getNamedItem("value").getNodeValue()); } - PolarisEclipseLinkMetaStoreSessionImpl.properties = properties; return properties; - } catch (Exception e) { - LOG.warn( - "Cannot find or parse the configuration file {} for persistence-unit {}", - confFile, - persistenceUnitName); + } catch (SAXException | IOException e) { + String str = + String.format( + "Cannot find or parse the configuration file %s for persistence-unit %s", + confFile, persistenceUnitName); + LOG.error(str); + throw new Exception(str); } - - return Maps.newHashMap(); } /** {@inheritDoc} */ diff --git a/extension/persistence/eclipselink/src/test/data/eclipselink_conf.jar b/extension/persistence/eclipselink/src/test/data/eclipselink_conf.jar new file mode 100644 index 0000000000..7f0a47fd7e Binary files /dev/null and b/extension/persistence/eclipselink/src/test/data/eclipselink_conf.jar differ diff --git a/extension/persistence/eclipselink/src/test/java/com/snowflake/polaris/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreTest.java b/extension/persistence/eclipselink/src/test/java/com/snowflake/polaris/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreTest.java index 03cc1026e9..7d707fc6a1 100644 --- a/extension/persistence/eclipselink/src/test/java/com/snowflake/polaris/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreTest.java +++ b/extension/persistence/eclipselink/src/test/java/com/snowflake/polaris/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreTest.java @@ -15,6 +15,10 @@ */ package com.snowflake.polaris.persistence.impl.eclipselink; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import io.polaris.core.PolarisCallContext; import io.polaris.core.PolarisConfigurationStore; import io.polaris.core.PolarisDefaultDiagServiceImpl; @@ -25,6 +29,12 @@ import io.polaris.extension.persistence.impl.eclipselink.PolarisEclipseLinkMetaStoreSessionImpl; import io.polaris.extension.persistence.impl.eclipselink.PolarisEclipseLinkStore; import java.time.ZoneId; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.Mockito; /** @@ -49,4 +59,30 @@ protected PolarisTestMetaStoreManager createPolarisTestMetaStoreManager() { new PolarisConfigurationStore() {}, timeSource.withZone(ZoneId.systemDefault()))); } + + @ParameterizedTest() + @ArgumentsSource(CreateStoreSessionArgs.class) + void testCreateStoreSession(String confFile, boolean success) { + PolarisDiagnostics diagServices = new PolarisDefaultDiagServiceImpl(); + PolarisEclipseLinkStore store = new PolarisEclipseLinkStore(diagServices); + try { + var session = + new PolarisEclipseLinkMetaStoreSessionImpl( + store, Mockito.mock(), () -> "realm", confFile, "polaris-dev"); + assertNotNull(session); + assertTrue(success); + } catch (Exception e) { + assertFalse(success); + } + } + + private static class CreateStoreSessionArgs implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of("META-INF/persistence.xml", true), + Arguments.of("eclipselink_conf.jar!/persistence.xml", true), + Arguments.of("/dummy_path/conf.jar!/persistence.xml", false)); + } + } } diff --git a/polaris-server.yml b/polaris-server.yml index cdfeb7027b..fba595668b 100644 --- a/polaris-server.yml +++ b/polaris-server.yml @@ -90,7 +90,8 @@ defaultRealms: metaStoreManager: type: in-memory # type: eclipse-link # uncomment to use eclipse-link as metastore - # persistence-unit: polaris-dev + # persistence-unit: polaris + # TODO - avoid duplicating token broker config oauth2: diff --git a/polaris-service/src/main/resources/META-INF/services/io.polaris.core.persistence.MetaStoreManagerFactory b/polaris-service/src/main/resources/META-INF/services/io.polaris.core.persistence.MetaStoreManagerFactory index 795cbfb8c2..00e5cbc69f 100644 --- a/polaris-service/src/main/resources/META-INF/services/io.polaris.core.persistence.MetaStoreManagerFactory +++ b/polaris-service/src/main/resources/META-INF/services/io.polaris.core.persistence.MetaStoreManagerFactory @@ -14,7 +14,5 @@ # limitations under the License. # -io.polaris.extension.persistence.impl.hibernate.HibernatePolarisMetaStoreManagerFactory io.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory -com.snowflake.polaris.persistence.impl.remote.RemotePolarisMetaStoreManagerFactory io.polaris.extension.persistence.impl.eclipselink.EclipseLinkPolarisMetaStoreManagerFactory diff --git a/polaris-service/src/test/resources/META-INF/persistence.xml b/polaris-service/src/test/resources/META-INF/persistence.xml index 11828b2848..db8c1c4bd3 100644 --- a/polaris-service/src/test/resources/META-INF/persistence.xml +++ b/polaris-service/src/test/resources/META-INF/persistence.xml @@ -20,7 +20,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> - + org.eclipse.persistence.jpa.PersistenceProvider io.polaris.core.persistence.models.ModelEntity io.polaris.core.persistence.models.ModelEntityActive diff --git a/polaris-service/src/test/resources/polaris-server-integrationtest.yml b/polaris-service/src/test/resources/polaris-server-integrationtest.yml index 78ff8190de..1924cb0b1c 100644 --- a/polaris-service/src/test/resources/polaris-server-integrationtest.yml +++ b/polaris-service/src/test/resources/polaris-server-integrationtest.yml @@ -85,6 +85,8 @@ metaStoreManager: type: in-memory # type: remote # url: http://sdp-devvm-mcollado:8080 +# type: eclipse-link # uncomment to use eclipse-link as metastore +# persistence-unit: polaris-dev oauth2: type: default