From dd201f86a7cd931bd8060022555a84bf91d3e6fe Mon Sep 17 00:00:00 2001 From: lukaseder Date: Thu, 24 Aug 2017 12:37:36 +0200 Subject: [PATCH] [#6517] JPADatabase should map JPA AttributeConverter types to generated jOOQ Converter --- .../util/jpa/AttributeConverterExtractor.java | 167 +++++++++++++++++ .../util/jpa/PersistenceUnitInfoImpl.java | 176 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/AttributeConverterExtractor.java create mode 100644 jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/PersistenceUnitInfoImpl.java diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/AttributeConverterExtractor.java b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/AttributeConverterExtractor.java new file mode 100644 index 0000000000..39512d2f32 --- /dev/null +++ b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/AttributeConverterExtractor.java @@ -0,0 +1,167 @@ +/* + * Licensed 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.util.jpa; + +import static org.jooq.impl.DSL.name; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.persistence.AttributeConverter; +import javax.persistence.EntityManagerFactory; +import javax.persistence.spi.PersistenceUnitInfo; + +import org.jooq.Name; +import org.jooq.tools.JooqLogger; + +import org.hibernate.boot.Metadata; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; +import org.hibernate.jpa.boot.spi.IntegratorProvider; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Table; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; +import org.hibernate.type.Type; +import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; + +/** + * A Hibernate {@link Integrator} that walks the meta model to discover all + * {@link AttributeConverter} references and how they're tied to database + * columns. + *

+ * This class was implemented with the help of Vlad Mihalcea's excellent blog + * post about walking the Hibernate meta model: https://vladmihalcea.com/2017/08/24/how-to-get-the-entity-mapping-to-database-table-binding-metadata-from-hibernate/ + * + * @author Lukas Eder + */ +final class AttributeConverterExtractor implements Integrator { + + static final JooqLogger log = JooqLogger.getLogger(JPADatabase.class); + + private Metadata meta; + private JPADatabase database; + private Collection> classes; + + AttributeConverterExtractor(JPADatabase database, Collection> classes) { + this.database = database; + this.classes = classes; + } + + @Override + public final void integrate(Metadata m, SessionFactoryImplementor f, SessionFactoryServiceRegistry r) { + this.meta = m; + } + + @Override + public final void disintegrate(SessionFactoryImplementor f, SessionFactoryServiceRegistry r) {} + + @SuppressWarnings("unchecked") + final Map> extract() { + Map> result = new LinkedHashMap>(); + + initEntityManagerFactory(); + for (PersistentClass persistentClass : meta.getEntityBindings()) { + Table table = persistentClass.getTable(); + + Iterator propertyIterator = persistentClass.getPropertyIterator(); + + propertyLoop: + while (propertyIterator.hasNext()) { + Property property = propertyIterator.next(); + Type type = property.getValue().getType(); + + if (type instanceof AttributeConverterTypeAdapter) { + AttributeConverter converter = ((AttributeConverterTypeAdapter) type).getAttributeConverter(); + Iterator columnIterator = property.getColumnIterator(); + + if (columnIterator.hasNext()) { + Column column = columnIterator.next(); + + if (columnIterator.hasNext()) { + log.info("AttributeConverter", "Cannot apply AttributeConverter of property " + property + " on several columns."); + continue propertyLoop; + } + + result.put(name(table.getCatalog(), table.getSchema(), table.getName(), column.getName()), converter); + } + } + } + } + + return result; + } + + private final EntityManagerFactory initEntityManagerFactory() { + PersistenceUnitInfo persistenceUnitInfo = persistenceUnitInfo(getClass().getSimpleName()); + Map configuration = new HashMap(); + configuration.put("hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList(this)); + configuration.put(AvailableSettings.CONNECTION_PROVIDER, database.connectionProvider()); + PersistenceUnitInfoDescriptor descriptor = new PersistenceUnitInfoDescriptor(persistenceUnitInfo); + return new EntityManagerFactoryBuilderImpl(descriptor, configuration).build(); + } + + private final PersistenceUnitInfoImpl persistenceUnitInfo(String name) { + return new PersistenceUnitInfoImpl(name, entityClassNames(), properties()); + } + + private final Properties properties() { + Properties properties = new Properties(); + properties.put("hibernate.dialect", JPADatabase.HIBERNATE_DIALECT); + properties.put("hibernate.hbm2ddl.auto", "create-drop"); + return properties; + } + + private final List entityClassNames() { + List result = new ArrayList(); + + for (Class klass : classes) + result.add(klass.getName()); + + return result; + } +} diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/PersistenceUnitInfoImpl.java b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/PersistenceUnitInfoImpl.java new file mode 100644 index 0000000000..de477a57cc --- /dev/null +++ b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/PersistenceUnitInfoImpl.java @@ -0,0 +1,176 @@ +/* + * Licensed 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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.util.jpa; + + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +/** + * A programmatic implementation of the persistence unit information. + *

+ * See also: https://vladmihalcea.com/2015/11/26/how-to-bootstrap-hibernate-without-the-persistence-xml-file/ + * + * @author Vlad Mihalcea + * @author Lukas Eder + */ +final class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + + private final String persistenceUnitName; + private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + private final List managedClassNames; + private final List mappingFileNames = new ArrayList(); + private final Properties properties; + private DataSource jtaDataSource; + private DataSource nonJtaDataSource; + + PersistenceUnitInfoImpl(String persistenceUnitName, List managedClassNames, Properties properties) { + this.persistenceUnitName = persistenceUnitName; + this.managedClassNames = managedClassNames; + this.properties = properties; + } + + @Override + public final String getPersistenceUnitName() { + return persistenceUnitName; + } + + @Override + public final String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public final PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + @Override + public final DataSource getJtaDataSource() { + return jtaDataSource; + } + + public final PersistenceUnitInfoImpl setJtaDataSource(DataSource jtaDataSource) { + this.jtaDataSource = jtaDataSource; + this.nonJtaDataSource = null; + this.transactionType = PersistenceUnitTransactionType.JTA; + + return this; + } + + @Override + public final DataSource getNonJtaDataSource() { + return nonJtaDataSource; + } + + public final PersistenceUnitInfoImpl setNonJtaDataSource(DataSource nonJtaDataSource) { + this.nonJtaDataSource = nonJtaDataSource; + this.jtaDataSource = null; + this.transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + + return this; + } + + @Override + public final List getMappingFileNames() { + return mappingFileNames; + } + + @Override + public final List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public final URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public final List getManagedClassNames() { + return managedClassNames; + } + + @Override + public final boolean excludeUnlistedClasses() { + return false; + } + + @Override + public final SharedCacheMode getSharedCacheMode() { + return SharedCacheMode.UNSPECIFIED; + } + + @Override + public final ValidationMode getValidationMode() { + return ValidationMode.AUTO; + } + + @Override + public final Properties getProperties() { + return properties; + } + + @Override + public final String getPersistenceXMLSchemaVersion() { + return "2.1"; + } + + @Override + public final ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public final void addTransformer(ClassTransformer transformer) {} + + @Override + public final ClassLoader getNewTempClassLoader() { + return null; + } +} \ No newline at end of file