You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by em...@apache.org on 2025/01/06 17:49:08 UTC
(polaris) branch main updated: EclipseLink: allow reading persistence.xml from the filesystem (#613)
This is an automated email from the ASF dual-hosted git repository.
emaynard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 6bda0782 EclipseLink: allow reading persistence.xml from the filesystem (#613)
6bda0782 is described below
commit 6bda07826d4ffec145b2bc362fbdf037f0e4771c
Author: Alexandre Dutra <ad...@users.noreply.github.com>
AuthorDate: Mon Jan 6 18:49:04 2025 +0100
EclipseLink: allow reading persistence.xml from the filesystem (#613)
---
extension/persistence/eclipselink/build.gradle.kts | 9 -
.../PolarisEclipseLinkMetaStoreSessionImpl.java | 119 ++---------
.../PolarisEclipseLinkPersistenceUnit.java | 224 +++++++++++++++++++++
.../PolarisEclipseLinkMetaStoreManagerTest.java | 38 +++-
.../persistence/impl/eclipselink/test-conf.jar | Bin 0 -> 1434 bytes
5 files changed, 272 insertions(+), 118 deletions(-)
diff --git a/extension/persistence/eclipselink/build.gradle.kts b/extension/persistence/eclipselink/build.gradle.kts
index 0c167f7a..96675b33 100644
--- a/extension/persistence/eclipselink/build.gradle.kts
+++ b/extension/persistence/eclipselink/build.gradle.kts
@@ -59,12 +59,3 @@ dependencies {
testImplementation(libs.mockito.core)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
-
-tasks.register<Jar>("archiveConf") {
- archiveFileName = "conf.jar"
- destinationDirectory = layout.buildDirectory.dir("conf")
-
- from("src/main/resources/META-INF/") { include("persistence.xml") }
-}
-
-tasks.named("test") { dependsOn("archiveConf") }
diff --git a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
index 71069521..7950075c 100644
--- a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
+++ b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
@@ -18,9 +18,6 @@
*/
package org.apache.polaris.extension.persistence.impl.eclipselink;
-import static org.eclipse.persistence.config.PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML;
-import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicates;
import jakarta.annotation.Nonnull;
@@ -29,14 +26,9 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
-import jakarta.persistence.Persistence;
import jakarta.persistence.PersistenceException;
-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.io.UncheckedIOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -45,13 +37,6 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisBaseEntity;
@@ -78,10 +63,6 @@ import org.apache.polaris.jpa.models.ModelGrantRecord;
import org.apache.polaris.jpa.models.ModelPrincipalSecrets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-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
@@ -141,56 +122,18 @@ public class PolarisEclipseLinkMetaStoreSessionImpl implements PolarisMetaStoreS
@Nullable String confFile,
@Nullable String persistenceUnitName) {
String realm = realmContext.getRealmIdentifier();
- EntityManagerFactory factory = realmFactories.getOrDefault(realm, null);
- if (factory != null) {
- return factory;
- }
-
- 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-INF/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();
- }
-
- LOGGER.debug(
- "Creating a new ClassLoader with the jar {} in classpath to load the config file",
- prefixUrl);
-
- URLClassLoader currentClassLoader =
- new URLClassLoader(new URL[] {prefixUrl}, this.getClass().getClassLoader());
-
- LOGGER.debug("Update ClassLoader in current thread temporarily");
- 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}", realm));
- }
- properties.put(ECLIPSELINK_PERSISTENCE_XML, confFile);
-
- factory = Persistence.createEntityManagerFactory(persistenceUnitName, properties);
- realmFactories.putIfAbsent(realm, factory);
-
- return factory;
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- Thread.currentThread().setContextClassLoader(prevClassLoader);
- }
+ return realmFactories.computeIfAbsent(
+ realm,
+ key -> {
+ try {
+ PolarisEclipseLinkPersistenceUnit persistenceUnit =
+ PolarisEclipseLinkPersistenceUnit.locatePersistenceUnit(
+ confFile, persistenceUnitName);
+ return persistenceUnit.createEntityManagerFactory(realmContext);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
}
@VisibleForTesting
@@ -198,42 +141,6 @@ public class PolarisEclipseLinkMetaStoreSessionImpl implements PolarisMetaStoreS
realmFactories.clear();
}
- /** Load the persistence unit properties from a given configuration file */
- private Map<String, String> loadProperties(
- @Nonnull String confFile, @Nonnull String persistenceUnitName) throws IOException {
- try {
- InputStream input =
- Thread.currentThread().getContextClassLoader().getResourceAsStream(confFile);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document doc = builder.parse(input);
- XPath xPath = XPathFactory.newInstance().newXPath();
- String expression =
- "/persistence/persistence-unit[@name='" + persistenceUnitName + "']/properties/property";
- NodeList nodeList =
- (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);
- Map<String, String> properties = new HashMap<>();
- for (int i = 0; i < nodeList.getLength(); i++) {
- NamedNodeMap nodeMap = nodeList.item(i).getAttributes();
- properties.put(
- nodeMap.getNamedItem("name").getNodeValue(),
- nodeMap.getNamedItem("value").getNodeValue());
- }
-
- return properties;
- } catch (XPathExpressionException
- | ParserConfigurationException
- | SAXException
- | IOException e) {
- String str =
- String.format(
- "Cannot find or parse the configuration file %s for persistence-unit %s",
- confFile, persistenceUnitName);
- LOGGER.error(str, e);
- throw new IOException(str);
- }
- }
-
/** {@inheritDoc} */
@Override
public <T> T runInTransaction(
diff --git a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkPersistenceUnit.java b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkPersistenceUnit.java
new file mode 100644
index 00000000..7ea8749f
--- /dev/null
+++ b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkPersistenceUnit.java
@@ -0,0 +1,224 @@
+/*
+ * 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 org.apache.polaris.extension.persistence.impl.eclipselink;
+
+import static org.eclipse.persistence.config.PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML;
+import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Persistence;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.extension.persistence.impl.eclipselink.PolarisEclipseLinkPersistenceUnit.ClasspathResourcePolarisEclipseLinkPersistenceUnit;
+import org.apache.polaris.extension.persistence.impl.eclipselink.PolarisEclipseLinkPersistenceUnit.FileSystemPolarisEclipseLinkPersistenceUnit;
+import org.apache.polaris.extension.persistence.impl.eclipselink.PolarisEclipseLinkPersistenceUnit.JarFilePolarisEclipseLinkPersistenceUnit;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+sealed interface PolarisEclipseLinkPersistenceUnit
+ permits ClasspathResourcePolarisEclipseLinkPersistenceUnit,
+ FileSystemPolarisEclipseLinkPersistenceUnit,
+ JarFilePolarisEclipseLinkPersistenceUnit {
+
+ EntityManagerFactory createEntityManagerFactory(RealmContext realmContext) throws IOException;
+
+ record ClasspathResourcePolarisEclipseLinkPersistenceUnit(
+ URL resource, String resourceName, String persistenceUnitName)
+ implements PolarisEclipseLinkPersistenceUnit {
+
+ @Override
+ public EntityManagerFactory createEntityManagerFactory(RealmContext realmContext)
+ throws IOException {
+ Map<String, String> properties = loadProperties(resource, persistenceUnitName, realmContext);
+ properties.put(ECLIPSELINK_PERSISTENCE_XML, resourceName);
+ return Persistence.createEntityManagerFactory(persistenceUnitName, properties);
+ }
+ }
+
+ record FileSystemPolarisEclipseLinkPersistenceUnit(Path path, String persistenceUnitName)
+ implements PolarisEclipseLinkPersistenceUnit {
+
+ @Override
+ public EntityManagerFactory createEntityManagerFactory(RealmContext realmContext)
+ throws IOException {
+ Map<String, String> properties =
+ loadProperties(path.toUri().toURL(), persistenceUnitName, realmContext);
+ Path archiveDirectory = path.getParent();
+ String descriptorPath = archiveDirectory.getParent().relativize(path).toString();
+ properties.put(ECLIPSELINK_PERSISTENCE_XML, descriptorPath);
+ ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
+ try (URLClassLoader currentClassLoader =
+ new URLClassLoader(
+ new URL[] {archiveDirectory.getParent().toUri().toURL()},
+ this.getClass().getClassLoader())) {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ return Persistence.createEntityManagerFactory(persistenceUnitName, properties);
+ } finally {
+ Thread.currentThread().setContextClassLoader(prevClassLoader);
+ }
+ }
+ }
+
+ record JarFilePolarisEclipseLinkPersistenceUnit(
+ URL confUrl, URL jarUrl, String descriptorPath, String persistenceUnitName)
+ implements PolarisEclipseLinkPersistenceUnit {
+
+ @Override
+ public EntityManagerFactory createEntityManagerFactory(RealmContext realmContext)
+ throws IOException {
+ Map<String, String> properties = loadProperties(confUrl, persistenceUnitName, realmContext);
+ properties.put(ECLIPSELINK_PERSISTENCE_XML, descriptorPath);
+ ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
+ try (URLClassLoader currentClassLoader =
+ new URLClassLoader(new URL[] {jarUrl}, this.getClass().getClassLoader())) {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ return Persistence.createEntityManagerFactory(persistenceUnitName, properties);
+ } finally {
+ Thread.currentThread().setContextClassLoader(prevClassLoader);
+ }
+ }
+ }
+
+ static PolarisEclipseLinkPersistenceUnit locatePersistenceUnit(
+ String confFile, String persistenceUnitName) throws IOException {
+ if (persistenceUnitName == null) {
+ persistenceUnitName = "polaris";
+ }
+ if (confFile == null) {
+ confFile = "META-INF/persistence.xml";
+ }
+ // Try an embedded config file first
+ int splitPosition = confFile.indexOf("!/");
+ if (splitPosition != -1) {
+ String jarPrefix = confFile.substring(0, splitPosition);
+ String descriptorPath = confFile.substring(splitPosition + 2);
+ URL jarUrl = classpathResource(jarPrefix);
+ if (jarUrl != null) {
+ // The JAR is in the classpath
+ URL confUrl = URI.create("jar:" + jarUrl + "!/" + descriptorPath).toURL();
+ return new ClasspathResourcePolarisEclipseLinkPersistenceUnit(
+ confUrl, confFile, persistenceUnitName);
+ } else {
+ // The JAR is in the filesystem
+ jarUrl = fileSystemPath(jarPrefix).toUri().toURL();
+ URL confUrl = URI.create("jar:" + jarUrl + "!/" + descriptorPath).toURL();
+ return new JarFilePolarisEclipseLinkPersistenceUnit(
+ confUrl, jarUrl, descriptorPath, persistenceUnitName);
+ }
+ }
+ // Try a classpath resource next
+ URL resource = classpathResource(confFile);
+ if (resource != null) {
+ return new ClasspathResourcePolarisEclipseLinkPersistenceUnit(
+ resource, confFile, persistenceUnitName);
+ }
+ // Try a filesystem path last
+ try {
+ return new FileSystemPolarisEclipseLinkPersistenceUnit(
+ fileSystemPath(confFile), persistenceUnitName);
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot find classpath resource or file: " + confFile, e);
+ }
+ }
+
+ private static Path fileSystemPath(String pathStr) {
+ Path path = Paths.get(pathStr);
+ if (!Files.exists(path) || !Files.isRegularFile(path)) {
+ throw new IllegalStateException("Not a regular file: " + pathStr);
+ }
+ return path.normalize().toAbsolutePath();
+ }
+
+ @Nullable
+ private static URL classpathResource(String resourceName) throws IOException {
+ Enumeration<URL> resources =
+ Thread.currentThread().getContextClassLoader().getResources(resourceName);
+ if (resources.hasMoreElements()) {
+ URL resource = resources.nextElement();
+ if (resources.hasMoreElements()) {
+ throw new IllegalStateException(
+ "Multiple resources found in classpath for " + resourceName);
+ }
+ return resource;
+ }
+ return null;
+ }
+
+ /** Load the persistence unit properties from a given configuration file */
+ private static Map<String, String> loadProperties(
+ @Nonnull URL confFile,
+ @Nonnull String persistenceUnitName,
+ @Nonnull RealmContext realmContext)
+ throws IOException {
+ try (InputStream input = confFile.openStream()) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(input);
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ String expression =
+ "/persistence/persistence-unit[@name='" + persistenceUnitName + "']/properties/property";
+ NodeList nodeList =
+ (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);
+ Map<String, String> properties = new HashMap<>();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ NamedNodeMap nodeMap = nodeList.item(i).getAttributes();
+ properties.put(
+ nodeMap.getNamedItem("name").getNodeValue(),
+ nodeMap.getNamedItem("value").getNodeValue());
+ }
+ // 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()));
+ }
+ return properties;
+ } catch (XPathExpressionException
+ | ParserConfigurationException
+ | SAXException
+ | IOException e) {
+ String str =
+ String.format(
+ "Cannot find or parse the configuration file %s for persistence-unit %s",
+ confFile, persistenceUnitName);
+ throw new IOException(str, e);
+ }
+ }
+}
diff --git a/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java b/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
index bbee4e37..9fbf2167 100644
--- a/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
+++ b/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
@@ -25,7 +25,11 @@ 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 java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.time.ZoneId;
+import java.util.Objects;
import java.util.stream.Stream;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.PolarisConfigurationStore;
@@ -204,11 +208,39 @@ public class PolarisEclipseLinkMetaStoreManagerTest extends BasePolarisMetaStore
private static class CreateStoreSessionArgs implements ArgumentsProvider {
@Override
- public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
+ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext)
+ throws URISyntaxException {
+ Path persistenceXml =
+ Paths.get(
+ Objects.requireNonNull(getClass().getResource("/META-INF/persistence.xml")).toURI());
+ Path confJar =
+ Paths.get(
+ Objects.requireNonNull(
+ getClass()
+ .getResource(
+ "/org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar"))
+ .toURI());
return Stream.of(
+ // conf file not provided
+ Arguments.of(null, true),
+ // classpath resource
Arguments.of("META-INF/persistence.xml", true),
- Arguments.of("./build/conf/conf.jar!/persistence.xml", true),
- Arguments.of("/dummy_path/conf.jar!/persistence.xml", false));
+ Arguments.of("META-INF/dummy.xml", false),
+ // classpath resource, embedded
+ Arguments.of(
+ "org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar!/persistence.xml",
+ true),
+ Arguments.of(
+ "org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar!/dummy.xml",
+ false),
+ Arguments.of("dummy/test-conf.jar!/persistence.xml", false),
+ // filesystem path
+ Arguments.of(persistenceXml.toString(), true),
+ Arguments.of("/dummy_path/conf/persistence.xml", false),
+ // filesystem path, embedded
+ Arguments.of(confJar + "!/persistence.xml", true),
+ Arguments.of(confJar + "!/dummy.xml", false),
+ Arguments.of("/dummy_path/test-conf.jar!/persistence.xml", false));
}
}
}
diff --git a/extension/persistence/eclipselink/src/test/resources/org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar b/extension/persistence/eclipselink/src/test/resources/org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar
new file mode 100644
index 00000000..2e42cc11
Binary files /dev/null and b/extension/persistence/eclipselink/src/test/resources/org/apache/polaris/extension/persistence/impl/eclipselink/test-conf.jar differ