Skip to content

Commit

Permalink
Automatically add jar to the classpath and resolve the embedded confi…
Browse files Browse the repository at this point in the history
…guration file
  • Loading branch information
aihuaxu committed Aug 6, 2024
1 parent cf557b3 commit 9e925cd
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 36 deletions.
8 changes: 8 additions & 0 deletions extension/persistence/eclipselink/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ dependencies {

testImplementation(libs.h2)
testImplementation(testFixtures(project(":polaris-core")))

sourceSets {
test {
resources {
srcDir 'src/test/data'
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,6 +46,8 @@
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;
Expand All @@ -69,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
Expand All @@ -82,7 +84,6 @@ public class PolarisEclipseLinkMetaStoreSessionImpl implements PolarisMetaStoreS
private ThreadLocal<EntityManager> localSession = new ThreadLocal<>();
private final PolarisEclipseLinkStore store;
private final PolarisStorageIntegrationProvider storageIntegrationProvider;
private static volatile Map<String, String> properties;

/**
* Create a meta store session against provided realm. Each realm has its own database.
Expand All @@ -99,47 +100,64 @@ public PolarisEclipseLinkMetaStoreSessionImpl(
@NotNull RealmContext realmContext,
@Nullable String confFile,
@Nullable String persistenceUnitName) {
persistenceUnitName = persistenceUnitName == null ? "polaris" : persistenceUnitName;
Map<String, String> 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);

createEntityManagerFactory(realmContext, confFile, persistenceUnitName);
LOG.debug("Create EclipseLink Meta Store Session for {}", realmContext.getRealmIdentifier());

// init store
this.store = store;
this.storageIntegrationProvider = storageIntegrationProvider;
}

/** Load the persistence unit properties from a given configuration file */
private Map<String, String> loadProperties(
@NotNull String confFile, @NotNull String persistenceUnitName) {
if (this.properties != null) {
return this.properties;
}

private void createEntityManagerFactory(
@NotNull RealmContext realmContext,
@Nullable String confFile,
@Nullable String persistenceUnitName) {
ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
try {
InputStream input = null;
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) {
input = this.getClass().getClassLoader().getResourceAsStream(confFile);
} else {
if (splitPosition != -1) {
String jarPrefixPath = confFile.substring(0, splitPosition);
String descPath = confFile.substring(splitPosition + 2);
confFile = confFile.substring(splitPosition + 2);
URL prefixUrl = this.getClass().getClassLoader().getResource(jarPrefixPath);
URLClassLoader child =
if (prefixUrl == null) {
prefixUrl = new File(jarPrefixPath).toURI().toURL();
}
URLClassLoader currentClassLoader =
new URLClassLoader(new URL[] {prefixUrl}, this.getClass().getClassLoader());
input = child.getResourceAsStream(descPath);
Thread.currentThread().setContextClassLoader(currentClassLoader);
}

Map<String, String> 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);

emf = 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<String, String> loadProperties(
@NotNull String confFile, @NotNull String persistenceUnitName) throws Exception {
try {
InputStream input =
Thread.currentThread().getContextClassLoader().getResourceAsStream(confFile);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(input);
Expand All @@ -156,16 +174,15 @@ private Map<String, String> loadProperties(
nodeMap.getNamedItem("value").getNodeValue());
}

this.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} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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);
}
}

protected static class CreateStoreSessionArgs implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9e925cd

Please sign in to comment.