You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/02/05 17:17:07 UTC
[syncope] 02/02: [SYNCOPE-1540] Use JPA metamodel to guess the
right case for column and table names
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
commit dce8eece79094df785bdaaa3b7e98d007f2db3ba
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Feb 5 17:57:28 2020 +0100
[SYNCOPE-1540] Use JPA metamodel to guess the right case for column and table names
---
.../jpa/content/XMLContentExporter.java | 161 ++++++++++++++++++++-
.../jpa/outer/XMLContentExporterTest.java | 12 +-
2 files changed, 160 insertions(+), 13 deletions(-)
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
index 44f1411..f70cbbf 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.persistence.jpa.content;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.Connection;
@@ -39,22 +40,37 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.JoinTable;
+import javax.persistence.Table;
+import javax.persistence.metamodel.Attribute;
+import javax.persistence.metamodel.EntityType;
+import javax.persistence.metamodel.PluralAttribute;
import javax.sql.DataSource;
-import javax.xml.bind.DatatypeConverter;
import javax.xml.XMLConstants;
+import javax.xml.bind.DatatypeConverter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
+import org.apache.commons.collections4.BidiMap;
+import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.content.ContentExporter;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.jpa.entity.JPAAccessToken;
@@ -73,8 +89,10 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrUniqueVa
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrValue;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
@@ -228,11 +246,44 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
return res;
}
+ private String columnName(final Supplier<Stream<Attribute<?, ?>>> attrs, final String columnName) {
+ String name = attrs.get().map(attr -> {
+ if (attr.getName().equalsIgnoreCase(columnName)) {
+ return attr.getName();
+ }
+
+ Field field = (Field) attr.getJavaMember();
+ Column column = field.getAnnotation(Column.class);
+ if (column != null && column.name().equalsIgnoreCase(columnName)) {
+ return column.name();
+ }
+
+ return null;
+ }).filter(Objects::nonNull).findFirst().orElse(columnName);
+
+ if (StringUtils.endsWithIgnoreCase(name, "_ID")) {
+ String left = StringUtils.substringBefore(name, "_");
+ String prefix = attrs.get().filter(attr -> left.equalsIgnoreCase(attr.getName())).findFirst().
+ map(Attribute::getName).orElse(left);
+ name = prefix + "_id";
+ }
+
+ return name;
+ }
+
+ private boolean isTask(final String tableName) {
+ return "TASK".equalsIgnoreCase(tableName);
+ }
+
+ @SuppressWarnings("unchecked")
private void exportTable(
final TransformerHandler handler,
final Connection conn,
final String tableName,
- final String whereClause) throws SQLException, SAXException {
+ final String whereClause,
+ final BidiMap<String, EntityType<?>> entities,
+ final Set<EntityType<?>> taskEntities,
+ final Map<String, Pair<String, String>> relationTables) throws SQLException, SAXException {
LOG.debug("Export table {}", tableName);
@@ -280,6 +331,19 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
List<Map<String, String>> rows = new ArrayList<>();
+ Optional<EntityType<?>> entity = entities.entrySet().stream().
+ filter(entry -> entry.getKey().equalsIgnoreCase(tableName)).
+ findFirst().
+ map(Map.Entry::getValue);
+
+ String outputTableName = entity.isPresent()
+ ? entities.getKey(entity.get())
+ : relationTables.keySet().stream().filter(key -> tableName.equalsIgnoreCase(key)).findFirst().
+ orElse(tableName);
+ if (isTask(tableName)) {
+ outputTableName = "Task";
+ }
+
rs = stmt.executeQuery();
while (rs.next()) {
Map<String, String> row = new HashMap<>();
@@ -295,8 +359,28 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
if (value != null && (!COLUMNS_TO_BE_NULLIFIED.containsKey(tableName)
|| !COLUMNS_TO_BE_NULLIFIED.get(tableName).contains(columnName))) {
- row.put(columnName, value);
- LOG.debug("Add for table {}: {}=\"{}\"", tableName, columnName, value);
+ String name = columnName;
+ if (entity.isPresent()) {
+ name = columnName(
+ () -> (Stream<Attribute<?, ?>>) entity.get().getAttributes().stream(), columnName);
+ }
+
+ if (isTask(tableName)) {
+ name = columnName(
+ () -> taskEntities.stream().flatMap(e -> e.getAttributes().stream()), columnName);
+ }
+
+ if (relationTables.containsKey(outputTableName)) {
+ Pair<String, String> relationColumns = relationTables.get(outputTableName);
+ if (name.equalsIgnoreCase(relationColumns.getLeft())) {
+ name = relationColumns.getLeft();
+ } else if (name.equalsIgnoreCase(relationColumns.getRight())) {
+ name = relationColumns.getRight();
+ }
+ }
+
+ row.put(name, value);
+ LOG.debug("Add for table {}: {}=\"{}\"", outputTableName, name, value);
}
}
}
@@ -319,8 +403,8 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
AttributesImpl attrs = new AttributesImpl();
row.forEach((key, value) -> attrs.addAttribute("", "", key, "CDATA", value));
- handler.startElement("", "", tableName, attrs);
- handler.endElement("", "", tableName);
+ handler.startElement("", "", outputTableName, attrs);
+ handler.endElement("", "", outputTableName);
}
} finally {
if (rs != null) {
@@ -340,6 +424,60 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
}
}
+ private Set<EntityType<?>> taskEntities(final Set<EntityType<?>> entityTypes) {
+ return entityTypes.stream().filter(e -> e.getName().endsWith("Task")).collect(Collectors.toSet());
+ }
+
+ private BidiMap<String, EntityType<?>> entities(final Set<EntityType<?>> entityTypes) {
+ BidiMap<String, EntityType<?>> entities = new DualHashBidiMap<>();
+ entityTypes.forEach(entity -> {
+ Table table = entity.getBindableJavaType().getAnnotation(Table.class);
+ if (table != null) {
+ entities.put(table.name(), entity);
+ }
+ });
+
+ return entities;
+ }
+
+ private Map<String, Pair<String, String>> relationTables(final BidiMap<String, EntityType<?>> entities) {
+ Map<String, Pair<String, String>> relationTables = new HashMap<>();
+ entities.values().stream().forEach(e -> e.getAttributes().stream().
+ filter(a -> a.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC).
+ forEach(a -> {
+ String attrName = a.getName();
+
+ Field field = (Field) a.getJavaMember();
+ Column column = field.getAnnotation(Column.class);
+ if (column != null) {
+ attrName = column.name();
+ }
+
+ CollectionTable collectionTable = field.getAnnotation(CollectionTable.class);
+ if (collectionTable != null) {
+ relationTables.put(
+ collectionTable.name(),
+ Pair.of(attrName, collectionTable.joinColumns()[0].name()));
+ }
+
+ JoinTable joinTable = field.getAnnotation(JoinTable.class);
+ if (joinTable != null) {
+ String tableName = joinTable.name();
+ if (StringUtils.isBlank(tableName)) {
+ tableName = entities.getKey(e) + "_"
+ + entities.getKey((EntityType) ((PluralAttribute) a).getElementType());
+ }
+
+ relationTables.put(
+ tableName,
+ Pair.of(joinTable.joinColumns()[0].name(),
+ joinTable.inverseJoinColumns()[0].name()));
+ }
+ }));
+
+ return relationTables;
+ }
+
@Override
public void export(
final String domain,
@@ -399,10 +537,17 @@ public class XMLContentExporter extends AbstractContentDealer implements Content
LOG.debug("Tables to be exported {}", tableNames);
+ EntityManagerFactory emf = EntityManagerFactoryUtils.findEntityManagerFactory(
+ ApplicationContextProvider.getBeanFactory(), domain);
+ Set<EntityType<?>> entityTypes = emf == null ? Collections.emptySet() : emf.getMetamodel().getEntities();
+ BidiMap<String, EntityType<?>> entities = entities(entityTypes);
+
// then sort tables based on foreign keys and dump
for (String tableName : sortByForeignKeys(schema, conn, tableNames)) {
try {
- exportTable(handler, conn, tableName, TABLES_TO_BE_FILTERED.get(tableName.toUpperCase()));
+ exportTable(
+ handler, conn, tableName, TABLES_TO_BE_FILTERED.get(tableName.toUpperCase()),
+ entities, taskEntities(entityTypes), relationTables(entities));
} catch (Exception e) {
LOG.error("Failure exporting table {}", tableName, e);
}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
index 4dfc322..2c3d84f 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
@@ -53,13 +53,15 @@ public class XMLContentExporterTest extends AbstractTest {
String exported = baos.toString(Charset.defaultCharset());
assertTrue(StringUtils.isNotBlank(exported));
+ System.out.println("EEEEEEEEEEEEEE\n" + exported);
+
List<String> realms = IOUtils.readLines(
IOUtils.toInputStream(exported, Charset.defaultCharset()), Charset.defaultCharset()).stream().
- filter(row -> StringUtils.startsWithIgnoreCase(row.trim(), "<REALM")).collect(Collectors.toList());
+ filter(row -> row.trim().startsWith("<Realm")).collect(Collectors.toList());
assertEquals(4, realms.size());
- assertTrue(StringUtils.containsIgnoreCase(realms.get(0), "NAME=\"/\""));
- assertTrue(StringUtils.containsIgnoreCase(realms.get(1), "NAME=\"odd\""));
- assertTrue(StringUtils.containsIgnoreCase(realms.get(2), "NAME=\"even\""));
- assertTrue(StringUtils.containsIgnoreCase(realms.get(3), "NAME=\"two\""));
+ assertTrue(realms.get(0).contains("name=\"/\""));
+ assertTrue(realms.get(1).contains("name=\"odd\""));
+ assertTrue(realms.get(2).contains("name=\"even\""));
+ assertTrue(realms.get(3).contains("name=\"two\""));
}
}