Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,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;
Expand All @@ -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
Expand All @@ -80,7 +84,6 @@ public class PolarisEclipseLinkMetaStoreSessionImpl implements PolarisMetaStoreS
private final 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 @@ -97,19 +100,8 @@ 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);

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

// init store
Expand All @@ -118,13 +110,55 @@ public PolarisEclipseLinkMetaStoreSessionImpl(
}

/** Load the persistence unit properties from a given configuration file */
private Map<String, String> 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();
}
Comment on lines +128 to +133
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add logs here specifying the jar we found and how we're altering the classpath

Copy link
Contributor Author

@aihuaxu aihuaxu Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see the comments before merging. Let me add that in a follow up.

URLClassLoader currentClassLoader =
new URLClassLoader(new URL[] {prefixUrl}, this.getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(currentClassLoader);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a logging statement here stating that we're changing the classloader temporarily

}

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);

return Persistence.createEntityManagerFactory(persistenceUnitName, properties);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Comment on lines +149 to +151
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catch RuntimeException separately and just rethrow

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 = 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);
Expand All @@ -141,16 +175,15 @@ private Map<String, String> 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log the original exception so we can see the stacktrace

throw new Exception(str);
}

return Maps.newHashMap();
}

/** {@inheritDoc} */
Expand Down
Binary file not shown.
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);
}
}

private 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));
}
}
}
3 changes: 2 additions & 1 deletion polaris-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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">

<persistence-unit name="polaris" transaction-type="RESOURCE_LOCAL">
<persistence-unit name="polaris-dev" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>io.polaris.core.persistence.models.ModelEntity</class>
<class>io.polaris.core.persistence.models.ModelEntityActive</class>
Expand Down
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