http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java deleted file mode 100644 index 6b6dfdd..0000000 --- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.polygene.entitystore.jooq; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.Period; -import java.time.ZonedDateTime; -import org.jooq.DataType; -import org.jooq.impl.SQLDataType; -import org.jooq.types.Interval; - -class SqlType -{ - @SuppressWarnings( "unchecked" ) - static <T> DataType<T> getSqlDataTypeFor( Class<?> propertyType ) - { - if( String.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.VARCHAR; - } - if( Integer.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - if( Long.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.BIGINT; - } - if( Boolean.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.BOOLEAN; - } - if( Float.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.FLOAT; - } - if( Double.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.DOUBLE; - } - if( Instant.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.TIMESTAMPWITHTIMEZONE; - } - if( Interval.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.VARCHAR; - } - if( Period.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.VARCHAR; - } - if( LocalDate.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.LOCALDATE; - } - if( LocalTime.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.LOCALTIME; - } - if( LocalDateTime.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.LOCALDATETIME; - } - if( ZonedDateTime.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.OFFSETDATETIME; - } - if( OffsetDateTime.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.OFFSETDATETIME; - } - if( Character.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.CHAR( 1 ); - } - if( Short.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - if( Byte.class.isAssignableFrom( propertyType ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - if( propertyType.isPrimitive() ) - { - if( propertyType.equals( Integer.TYPE ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - if( propertyType.equals( Long.TYPE ) ) - { - return (DataType<T>) SQLDataType.BIGINT; - } - if( propertyType.equals( Boolean.TYPE ) ) - { - return (DataType<T>) SQLDataType.BOOLEAN; - } - if( propertyType.equals( Float.TYPE ) ) - { - return (DataType<T>) SQLDataType.FLOAT; - } - if( propertyType.equals( Double.TYPE ) ) - { - return (DataType<T>) SQLDataType.DOUBLE; - } - if( propertyType.equals( Character.TYPE ) ) - { - return (DataType<T>) SQLDataType.CHAR( 1 ); - } - if( propertyType.equals( Short.TYPE ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - if( propertyType.equals( Byte.TYPE ) ) - { - return (DataType<T>) SQLDataType.INTEGER; - } - } - return (DataType<T>) SQLDataType.VARCHAR; - } -}
http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java deleted file mode 100644 index db42413..0000000 --- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.polygene.entitystore.jooq; - -import java.sql.Timestamp; -import org.jooq.Field; - -import static org.apache.polygene.entitystore.jooq.TypesTable.makeField; - -public interface TableFields -{ - // Common in all tables - String IDENTITY_COLUMN_NAME = "_identity"; - String CREATED_COLUMN_NAME = "_created_at"; - String LASTMODIFIED_COLUMN_NAME = "_modified_at"; - - // Types Table - String TABLENAME_COLUMN_NAME = "_table_name"; - - // Entities Table - String VALUEID_COLUMN_NAME = "_value_id"; - String TYPE_COLUMN_NAME = "_type"; - String VERSION_COLUMN_NAME = "_version"; - String APPLICATIONVERSION_COLUMN_NAME = "_app_version"; - - // Mixin Tables - String NAME_COLUMN_NAME = "_name"; - String INDEX_COLUMN_NAME = "_index"; // either index in ManyAssociation or name in NamedAssociation - String REFERENCE_COLUMN_NAME = "_reference"; - String ASSOCS_TABLE_POSTFIX = "_ASSOCS"; - - - // Common Fields - Field<String> identityColumn = makeField( IDENTITY_COLUMN_NAME, String.class ); - Field<Timestamp> createdColumn = makeField( CREATED_COLUMN_NAME, Timestamp.class ); - Field<Timestamp> modifiedColumn = makeField( LASTMODIFIED_COLUMN_NAME, Timestamp.class ); - - // Types Table - Field<String> tableNameColumn = makeField( TABLENAME_COLUMN_NAME, String.class ); - - // Entities Table - Field<String> valueIdentityColumn = makeField( VALUEID_COLUMN_NAME, String.class ); - Field<String> typeNameColumn = makeField( TYPE_COLUMN_NAME, String.class ); - Field<String> versionColumn = makeField( VERSION_COLUMN_NAME, String.class ); - Field<String> applicationVersionColumn = makeField( APPLICATIONVERSION_COLUMN_NAME, String.class ); - - // Mixin Tables - - // The _ASSOCS table - Field<String> nameColumn = makeField( NAME_COLUMN_NAME, String.class ); - Field<String> referenceColumn = makeField( REFERENCE_COLUMN_NAME, String.class ); - Field<String> indexColumn = makeField( INDEX_COLUMN_NAME, String.class ); - -} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java deleted file mode 100644 index c816c95..0000000 --- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.polygene.entitystore.jooq; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.sql.Timestamp; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.polygene.api.association.AssociationDescriptor; -import org.apache.polygene.api.common.QualifiedName; -import org.apache.polygene.api.entity.EntityDescriptor; -import org.apache.polygene.api.property.Property; -import org.apache.polygene.api.property.PropertyDescriptor; -import org.apache.polygene.api.util.Classes; -import org.jooq.CreateTableColumnStep; -import org.jooq.DataType; -import org.jooq.Field; -import org.jooq.Record; -import org.jooq.Result; -import org.jooq.SQLDialect; -import org.jooq.Schema; -import org.jooq.Table; -import org.jooq.impl.DSL; - -public class TypesTable - implements TableFields -{ - private final Map<Class<?>, Table<Record>> mixinTablesCache = new ConcurrentHashMap<>(); - private final Map<Class<?>, Table<Record>> mixinAssocsTablesCache = new ConcurrentHashMap<>(); - - private final Table<Record> typesTable; - private final SQLDialect dialect; - private final Schema schema; - - private final JooqDslContext dsl; - - TypesTable( JooqDslContext dsl, Schema schema, - SQLDialect dialect, - String typesTablesName - ) - { - this.schema = schema; - this.dialect = dialect; - typesTable = tableOf( typesTablesName ); - this.dsl = dsl; - } - - static <T> Field<T> makeField( String columnName, Class<T> type ) - { - return DSL.field( DSL.name( columnName ), type ); - } - - Table<Record> tableOf( String tableName ) - { - return DSL.table( - dialect.equals( SQLDialect.SQLITE ) - ? DSL.name( tableName ) - : DSL.name( schema.getName(), tableName ) ); - } - - String tableNameOf( Class<?> mixinType ) - { - Result<Record> typeInfo = fetchTypeInfoFromTable( mixinType ); - if( typeInfo.isEmpty() ) - { - return null; - } - return typeInfo.getValue( 0, tableNameColumn ); - } - - Table<Record> tableFor( Class<?> type, EntityDescriptor descriptor ) - { - return mixinTablesCache.computeIfAbsent( type, t -> - { - String tableName = tableNameOf( t ); - if( tableName == null ) - { - Result<Record> newMixinTable = createNewMixinTable( type, descriptor ); - return tableOf( newMixinTable.getValue( 0, tableNameColumn ) ); - } - return tableOf( tableName ); - } ); - } - - private Result<Record> fetchTypeInfoFromTable( Class<?> mixinTableName ) - { - return dsl.select() - .from( typesTable ) - .where( identityColumn.eq( mixinTableName.getName() ) ) - .fetch(); - } - - private Result<Record> createNewMixinTable( Class<?> mixinType, EntityDescriptor descriptor ) - { - String mixinTypeName = mixinType.getName(); - String tableName = createNewTableName( mixinType ); - CreateTableColumnStep primaryTable = dsl.createTable( DSL.name( schema.getName(), tableName ) ) - .column( identityColumn ) - .column( createdColumn ); - descriptor.state().properties().forEach( - property -> - { - QualifiedName qualifiedName = property.qualifiedName(); - if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) ) - { - primaryTable.column( fieldOf( property ) ); - } - } ); - descriptor.state().associations().forEach( - assoc -> - { - QualifiedName qualifiedName = assoc.qualifiedName(); - if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) ) - { - primaryTable.column( fieldOf( assoc ) ); - } - } ); - int result1 = primaryTable.execute(); - int result3 = dsl.insertInto( typesTable ) - .set( identityColumn, mixinTypeName ) - .set( tableNameColumn, tableName ) - .set( createdColumn, new Timestamp( System.currentTimeMillis() ) ) - .set( modifiedColumn, new Timestamp( System.currentTimeMillis() ) ) - .execute(); - return fetchTypeInfoFromTable( mixinType ); - } - - private String createNewTableName( Class<?> mixinType ) - { - String typeName = mixinType.getSimpleName(); - String postFix = ""; - int counter = 0; - boolean found = false; - do - { - found = checkForTableNamed( typeName + postFix ); - postFix = "_" + counter++; - } while( found ); - return typeName; - } - - private boolean checkForTableNamed( String tableName ) - { - return dsl.select() - .from( typesTable ) - .where( tableNameColumn.eq( tableName ) ) - .fetch().size() > 0; - } - - private boolean isProperty( Method method ) - { - return Property.class.isAssignableFrom( method.getReturnType() ) && method.getParameterCount() == 0; - } - - Field<Object> fieldOf( PropertyDescriptor descriptor ) - { - String propertyName = descriptor.qualifiedName().name(); - return DSL.field( DSL.name( propertyName ), dataTypeOf( descriptor ) ); - } - - Field<String> fieldOf( AssociationDescriptor descriptor ) - { - String propertyName = descriptor.qualifiedName().name(); - return DSL.field( DSL.name( propertyName ), DSL.getDataType( String.class ) ); - } - - private <T> DataType<T> dataTypeOf( PropertyDescriptor property ) - { - Type type = property.type(); - Class<?> rawType = Classes.RAW_CLASS.apply( type ); - return SqlType.getSqlDataTypeFor( rawType ); - } -} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java deleted file mode 100644 index c251efe..0000000 --- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.polygene.entitystore.jooq.assembly; - -import org.apache.polygene.api.identity.Identity; -import org.apache.polygene.api.identity.StringIdentity; -import org.apache.polygene.bootstrap.Assembler; -import org.apache.polygene.bootstrap.Assemblers; -import org.apache.polygene.bootstrap.AssemblyException; -import org.apache.polygene.bootstrap.ModuleAssembly; -import org.apache.polygene.entitystore.jooq.JooqDslContext; -import org.apache.polygene.entitystore.jooq.JooqEntityStoreConfiguration; -import org.apache.polygene.entitystore.jooq.JooqEntityStoreService; -import org.jooq.SQLDialect; -import org.jooq.conf.RenderNameStyle; -import org.jooq.conf.Settings; - -/** - * JOOQ EntityStore assembly. - */ -@SuppressWarnings( "WeakerAccess" ) -public class JooqEntityStoreAssembler extends Assemblers.VisibilityIdentityConfig<JooqEntityStoreAssembler> - implements Assembler -{ - public static final Identity DEFAULT_ENTITYSTORE_IDENTITY = new StringIdentity( "entitystore-jooq" ); - - @Override - public void assemble( ModuleAssembly module ) - { - Settings settings = getSettings(); - if( settings == null ) - { - throw new AssemblyException( "Settings must not be null" ); - } - - String identity = ( hasIdentity() ? identity() : DEFAULT_ENTITYSTORE_IDENTITY ).toString(); - module.transients( JooqDslContext.class ); - - module.services( JooqEntityStoreService.class ) - .identifiedBy( identity ) - .visibleIn( visibility() ) - .instantiateOnStartup() - .setMetaInfo( settings ); - - if( hasConfig() ) - { - configModule().entities( JooqEntityStoreConfiguration.class ).visibleIn( configVisibility() ); - } - } - - protected Settings getSettings() - { - return new Settings().withRenderNameStyle( RenderNameStyle.QUOTED ); - } - - protected SQLDialect getSQLDialect() - { - return SQLDialect.DEFAULT; - } -} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java b/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java deleted file mode 100644 index f36db0e..0000000 --- a/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.polygene.entitystore.jooq; - -import org.apache.polygene.api.common.Visibility; -import org.apache.polygene.bootstrap.AssemblyException; -import org.apache.polygene.bootstrap.ModuleAssembly; -import org.apache.polygene.entitystore.jooq.assembly.JooqEntityStoreAssembler; -import org.apache.polygene.library.sql.assembly.DataSourceAssembler; -import org.apache.polygene.library.sql.datasource.DataSourceConfiguration; -import org.apache.polygene.library.sql.dbcp.DBCPDataSourceServiceAssembler; -import org.apache.polygene.test.EntityTestAssembler; -import org.apache.polygene.test.entity.AbstractEntityStoreTest; -import org.jooq.SQLDialect; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; - -public class JooqEntityStoreTest extends AbstractEntityStoreTest -{ - @Rule - public final TemporaryFolder tmpDir = new TemporaryFolder(); - - @Override - // START SNIPPET: assembly - public void assemble( ModuleAssembly module ) - throws AssemblyException - { - // END SNIPPET: assembly - super.assemble( module ); - module.defaultServices(); - ModuleAssembly config = module.layer().module( "config" ); - new EntityTestAssembler().visibleIn( Visibility.module ).assemble( config ); - - // START SNIPPET: assembly - // Assemble a DataSource - new DataSourceAssembler() - .withDataSourceServiceIdentity( "datasource" ) - .identifiedBy( "ds-mysql" ) - .visibleIn( Visibility.module ) - .assemble( module ); - - // Assemble the Apache DBCP based Service Importer - new DBCPDataSourceServiceAssembler() - .identifiedBy( "datasource" ) - .visibleIn( Visibility.module ) - .withConfig( config, Visibility.layer ) - .assemble( module ); - - new JooqEntityStoreAssembler() - .withConfig( config, Visibility.layer ) - .identifiedBy( "jooq-entitystore" ) - .assemble( module ); - // END SNIPPET: assembly - - JooqEntityStoreConfiguration jooqDefaults = config.forMixin( JooqEntityStoreConfiguration.class ) - .setMetaInfo( SQLDialect.H2 ) - .declareDefaults(); - jooqDefaults.entitiesTableName().set( "ENTITIES" ); - - DataSourceConfiguration dsDefaults = config.forMixin( DataSourceConfiguration.class ).declareDefaults(); - dsDefaults.driver().set( org.h2.Driver.class.getName() ); - dsDefaults.enabled().set( true ); - dsDefaults.maxPoolSize().set( 3 ); - dsDefaults.minPoolSize().set( 1 ); - dsDefaults.username().set( "" ); - dsDefaults.password().set( "" ); - dsDefaults.url().set( "jdbc:h2:" + tmpDir.getRoot().getAbsolutePath() + "/testdb;create=true" ); - // START SNIPPET: assembly - } - // END SNIPPET: assembly -} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/build.gradle ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/build.gradle b/extensions/entitystore-sql/build.gradle new file mode 100644 index 0000000..32ceb70 --- /dev/null +++ b/extensions/entitystore-sql/build.gradle @@ -0,0 +1,44 @@ +/* + * 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. + * + * + */ + +apply plugin: 'polygene-extension' + +description = "Apache Polygene⢠ORM EntityStore Extension" + +jar { manifest { name = "Apache Polygene⢠Extension - EntityStore - ORM" } } + +dependencies { + api polygene.core.bootstrap + api polygene.library( 'sql' ) + api libraries.jooq + + runtimeOnly polygene.core.runtime + + testImplementation polygene.internals.testsupport + testImplementation polygene.library( 'sql-dbcp' ) + testImplementation libraries.docker_junit + + testRuntimeOnly libraries.logback + testRuntimeOnly libraries.derby + testRuntimeOnly libraries.h2 + testRuntimeOnly libraries.mysql_connector + testRuntimeOnly libraries.postgres + testRuntimeOnly libraries.sqlite +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/dev-status.xml ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/dev-status.xml b/extensions/entitystore-sql/dev-status.xml new file mode 100644 index 0000000..b6d4c31 --- /dev/null +++ b/extensions/entitystore-sql/dev-status.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + ~ 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. + ~ + ~ + --> +<module xmlns="http://polygene.apache.org/schemas/2008/dev-status/1" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://polygene.apache.org/schemas/2008/dev-status/1 + http://polygene.apache.org/schemas/2008/dev-status/1/dev-status.xsd"> + <status> + <!--none,early,beta,stable,mature--> + <codebase>early</codebase> + + <!-- none, brief, good, complete --> + <documentation>brief</documentation> + + <!-- none, some, good, complete --> + <unittests>good</unittests> + </status> + <licenses> + <license>ALv2</license> + </licenses> +</module> http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/docs/es-sql.txt ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/docs/es-sql.txt b/extensions/entitystore-sql/src/docs/es-sql.txt new file mode 100644 index 0000000..a36d463 --- /dev/null +++ b/extensions/entitystore-sql/src/docs/es-sql.txt @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////// + * 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. +/////////////////////////////////////////////////////////////// + +[[extension-es-jooq,SQL EntityStore]] += ORM EntityStore = + +[devstatus] +-------------- +source=extensions/entitystore-jooq/dev-status.xml +-------------- + +This entitystore is backed by a SQL server, and maps each mixin type of the Composite into separate tables. This is more +enterprise-friendly, but comes at the cost of less performance compared to the <<extension-es-sql>>. + +This extension fully leverage the <<library-sql>> meaning that you must use it to assemble your DataSource and that you +get <<library-circuitbreaker,Circuit Breaker>> and <<library-jmx, JMX>> integration for free. + +include::../../build/docs/buildinfo/artifact.txt[] + +== Assembly == + +Assembly is done using the provided Assembler: + +[snippet,java] +---- +source=extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java +tag=assembly +---- + +== Configuration == + +Here are the available configuration properties: + +[snippet,java] +---- +source=extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreConfiguration.java +tag=config +---- + +All authentication related properties are optional. +By default no authentication is used. +As soon as you provide a `username`, authentication is set up. http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java new file mode 100644 index 0000000..9fb6771 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java @@ -0,0 +1,28 @@ +/* + * 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.polygene.entitystore.sql; + +import org.apache.polygene.api.common.QualifiedName; + +@SuppressWarnings( "WeakerAccess" ) +public class AssociationValue +{ + QualifiedName name; + String position; + String reference; +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java new file mode 100644 index 0000000..982366d --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java @@ -0,0 +1,34 @@ +/* + * 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.polygene.entitystore.sql; + +import java.time.Instant; +import org.apache.polygene.api.entity.EntityDescriptor; +import org.apache.polygene.api.identity.Identity; + +@SuppressWarnings( "WeakerAccess" ) +public class BaseEntity +{ + EntityDescriptor type; + Identity identity; + String version; + String applicationVersion; + Instant modifedAt; + Instant createdAt; + Identity currentValueIdentity; +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java new file mode 100644 index 0000000..d25e7fc --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java @@ -0,0 +1,363 @@ +/* + * 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.polygene.entitystore.sql; + +import java.lang.reflect.Method; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.common.QualifiedName; +import org.apache.polygene.api.composite.Composite; +import org.apache.polygene.api.entity.EntityComposite; +import org.apache.polygene.api.entity.EntityDescriptor; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.identity.HasIdentity; +import org.apache.polygene.api.identity.StringIdentity; +import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.EntityCompositeType; +import org.apache.polygene.api.unitofwork.NoSuchEntityTypeException; +import org.apache.polygene.api.util.Classes; +import org.apache.polygene.spi.entitystore.EntityNotFoundException; +import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork; +import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState; +import org.jooq.Condition; +import org.jooq.Field; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.Schema; +import org.jooq.SelectJoinStep; +import org.jooq.SelectQuery; +import org.jooq.Table; +import org.jooq.impl.DSL; + +@SuppressWarnings( "WeakerAccess" ) +public class EntitiesTable + implements TableFields +{ + private static final Predicate<? super Class<?>> NOT_COMPOSITE = type -> !( type.equals( Composite.class ) || type.equals( EntityComposite.class ) ); + private static final Predicate<? super Class<?>> NOT_HASIDENTITY = type -> !( type.equals( HasIdentity.class ) ); + private Map<EntityCompositeType, Set<Class<?>>> mixinTypeCache = new ConcurrentHashMap<>(); + private Map<Class<?>, MixinTable> mixinTablesCache = new ConcurrentHashMap<>(); + + private final Table<Record> entitiesTable; + private JooqDslContext dsl; + private final TypesTable types; + private final Schema schema; + private String applicationVersion; + private boolean replacementStrategy = false; // Figure out later if we should support both and if so, how. + + EntitiesTable( JooqDslContext dsl, Schema schema, TypesTable types, String applicationVersion, String entitiesTableName ) + { + this.dsl = dsl; + this.types = types; + this.schema = schema; + this.applicationVersion = applicationVersion; + entitiesTable = types.tableOf( entitiesTableName ); + } + + public BaseEntity fetchEntity( EntityReference reference, ModuleDescriptor module ) + { + + Result<Record> baseEntityResult = dsl + .selectFrom( entitiesTable ) + .where( identityColumn.eq( reference.identity().toString() ) ) + .fetch(); + + if( baseEntityResult.isEmpty() ) + { + throw new EntityNotFoundException( reference ); + } + Record row = baseEntityResult.get( 0 ); + return toBaseEntity( row, module ); + } + + protected BaseEntity toBaseEntity( Record row, ModuleDescriptor module ) + { + BaseEntity result = new BaseEntity(); + String typeName = row.field( typeNameColumn ).get( row ); + result.type = findEntityDescriptor( typeName, module ); + result.version = row.field( versionColumn ).get( row ); + result.applicationVersion = row.field( applicationVersionColumn ).get( row ); + result.identity = StringIdentity.identityOf( row.field( identityColumn ).get( row ) ); + result.currentValueIdentity = EntityReference.parseEntityReference( row.field( valueIdentityColumn ).get( row ) ).identity(); + result.modifedAt = Instant.ofEpochMilli( row.field( modifiedColumn ).get( row ).getTime() ); + result.createdAt = Instant.ofEpochMilli( row.field( createdColumn ).get( row ).getTime() ); + return result; + } + + public Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module ) + { + Result<Record> baseEntityResult = dsl + .selectFrom( entitiesTable ) + .fetch(); + return baseEntityResult.stream().map( record -> toBaseEntity( record, module ) ); + } + + private EntityDescriptor findEntityDescriptor( String typeName, ModuleDescriptor module ) + { + try + { + Class<?> type = getClass().getClassLoader().loadClass( typeName ); + return module.typeLookup().lookupEntityModel( type ); + } + catch( ClassNotFoundException e ) + { + throw new NoSuchEntityTypeException( typeName, module); + } + } + + void insertEntity( DefaultEntityState state, BaseEntity baseEntity ) + { + EntityCompositeType compositeType = state.entityDescriptor().valueType(); + Set<Class<?>> mixinTypes = mixinTypeCache.computeIfAbsent( compositeType, createMixinTypesSet( compositeType ) ); + mixinTypes.forEach( type -> + { + MixinTable table = findMixinTable( type, state.entityDescriptor() ); + table.insertMixinState( state, baseEntity.currentValueIdentity.toString() ); + } ); + } + + void modifyEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork uow ) + { + updateBaseEntity( baseEntity, uow ); + if( replacementStrategy ) + { + insertEntity( state, baseEntity ); // replacement strategy (more safe) + } + else + { + EntityCompositeType compositeType = state.entityDescriptor().valueType(); + Set<Class<?>> mixinTypes = mixinTypeCache.computeIfAbsent( compositeType, createMixinTypesSet( compositeType ) ); + mixinTypes.forEach( type -> + { + MixinTable table = findMixinTable( type, state.entityDescriptor() ); + table.modifyMixinState( state, baseEntity.currentValueIdentity.toString() ); + } ); + } + } + + private MixinTable findMixinTable( Class<?> type, EntityDescriptor entityDescriptor ) + { + return mixinTablesCache.computeIfAbsent( type, t -> new MixinTable( dsl, schema, types, type, entityDescriptor ) ); + } + + private Set<Class<?>> mixinsOf( Stream<? extends AssociationDescriptor> stream ) + { + return stream + .map( AssociationDescriptor::accessor ) + .filter( Classes.instanceOf( Method.class ) ) + .map( accessor -> (Method) accessor ) + .map( Method::getDeclaringClass ) + .filter( NOT_HASIDENTITY ) + .filter( NOT_COMPOSITE ) + .collect( Collectors.toSet() ); + } + + private Function<EntityCompositeType, Set<Class<?>>> createMixinTypesSet( EntityCompositeType compositeType ) + { + return type -> + { + Set<Class<?>> mixins = compositeType + .properties() + .map( PropertyDescriptor::accessor ) + .filter( Classes.instanceOf( Method.class ) ) + .map( accessor -> (Method) accessor ) + .map( Method::getDeclaringClass ) + .filter( NOT_HASIDENTITY ) + .filter( NOT_COMPOSITE ) + .collect( Collectors.toSet() ); + Set<Class<?>> mixinsWithAssociations = mixinsOf( compositeType.associations() ); + Set<Class<?>> mixinsWithManyAssociations = mixinsOf( compositeType.manyAssociations() ); + Set<Class<?>> mixinsWithNamedAssociations = mixinsOf( compositeType.namedAssociations() ); + mixins.addAll( mixinsWithAssociations ); + mixins.addAll( mixinsWithManyAssociations ); + mixins.addAll( mixinsWithNamedAssociations ); + return mixins; + }; + } + + void createNewBaseEntity( EntityReference reference, EntityDescriptor descriptor, EntityStoreUnitOfWork uow ) + { + String valueIdentity = UUID.randomUUID().toString(); + dsl.insertInto( entitiesTable ) + .set( identityColumn, reference.identity().toString() ) + .set( createdColumn, new Timestamp( uow.currentTime().toEpochMilli() ) ) + .set( modifiedColumn, new Timestamp( uow.currentTime().toEpochMilli() ) ) + .set( valueIdentityColumn, valueIdentity ) + .set( typeNameColumn, descriptor.primaryType().getName() ) + .set( versionColumn, "1" ) + .set( applicationVersionColumn, applicationVersion ) + .execute(); + } + + private void updateBaseEntity( BaseEntity entity, EntityStoreUnitOfWork uow ) + { + entity.version = increment( entity.version ); + if( replacementStrategy ) + { + entity.currentValueIdentity = StringIdentity.identityOf( UUID.randomUUID().toString() ); + } + dsl.update( entitiesTable ) + .set( modifiedColumn, new Timestamp( uow.currentTime().toEpochMilli() ) ) + .set( valueIdentityColumn, entity.currentValueIdentity.toString() ) + .set( versionColumn, entity.version ) + .set( applicationVersionColumn, applicationVersion ) + .execute(); + } + + private String increment( String version ) + { + long ver = Long.parseLong( version ); + return Long.toString( ver + 1 ); + } + + /** + * Builds the SELECT statement for a given entity. + * <p> + * Example; If we have the following entity + * </p> + * <code><pre> + * public interface LegalEntity + * { + * Property<String> registration(); + * } + * <p> + * public interface Person extends LegalEntity + * { + * Property<String> name(); + * <p> + * @Optional + * Association<Person> spouse(); + * <p> + * ManyAssocation<Person> children(); + * } + * </pre></code> + * <p> + * and we do a simple; + * <code><pre> + * Person p = uow.get( Person.class, "niclas" ); + * </pre></code> + * <p> + * then the generated query will be + * </p> + * <code><pre> + * SELECT * FROM ENTITIES + * JOIN Person ON identity = ENTITIES.value_id + * JOIN LegalEntity ON identity = ENTITIES.value_id + * JOIN Person_Assoc ON identity = ENTITIES.value_id + * WHERE ENTITIES.identity = '123' + * </pre></code> + * + * @param entityDescriptor The descriptor of the entity type to be built. + * @return The SELECT query that can be executed to retrieve the entity. + */ + public SelectQuery<Record> createGetEntityQuery( EntityDescriptor entityDescriptor, EntityReference reference ) + { + List<Table<Record>> joins = getMixinTables( entityDescriptor ); + SelectJoinStep<Record> from = dsl.select().from( entitiesTable ); + for( Table<Record> joinedTable : joins ) + { + Condition joinCondition = valueIdentityColumn.eq( identityColumnOf( joinedTable ) ); + from = from.leftJoin( joinedTable ).on( joinCondition ); + } + return from.where( identityColumnOf( entitiesTable ).eq( reference.identity().toString() ) ).getQuery(); + } + + public void fetchAssociations( BaseEntity entity, EntityDescriptor entityDescriptor, Consumer<AssociationValue> consume ) + { + List<Table<Record>> joins = getAssocationsTables( entityDescriptor ); + SelectJoinStep<Record> from = dsl.select().from( entitiesTable ); + for( Table<Record> joinedTable : joins ) + { + Condition joinCondition = valueIdentityColumn.eq( identityColumnOf( joinedTable ) ); + from = from.join( joinedTable ).on( joinCondition ); + } + String reference = entity.identity.toString(); + SelectQuery<Record> query = from.where( identityColumnOf( entitiesTable ).eq( reference ) ).getQuery(); + Result<Record> result = query.fetch(); + result.forEach( record -> + { + AssociationValue value = new AssociationValue(); + value.name = QualifiedName.fromClass( entityDescriptor.primaryType(), record.getValue( nameColumn ) ); + value.position = record.getValue( indexColumn ); + value.reference = record.getValue( referenceColumn ); + consume.accept( value ); + } ); + } + + private Field<String> identityColumnOf( Table<Record> joinedTable ) + { + return DSL.field( DSL.name( joinedTable.getName(), identityColumn.getName() ), String.class ); + } + + public List<Table<Record>> getMixinTables( EntityDescriptor entityDescriptor ) + { + return entityDescriptor + .mixinTypes() + .filter( NOT_COMPOSITE ) + .filter( NOT_HASIDENTITY ) + .map( ( Class<?> type ) -> types.tableFor( type, entityDescriptor ) ) + .collect( Collectors.toList() ); + } + + public List<Table<Record>> getAssocationsTables( EntityDescriptor entityDescriptor ) + { + return entityDescriptor + .mixinTypes() + .filter( NOT_COMPOSITE ) + .filter( NOT_HASIDENTITY ) + .map( type -> findMixinTable( type, entityDescriptor ) ) + .map( MixinTable::associationsTable ) + .collect( Collectors.toList() ); + } + + public void removeEntity( EntityReference entityReference, EntityDescriptor descriptor ) + { + ModuleDescriptor module = descriptor.module(); + BaseEntity baseEntity = fetchEntity( entityReference, module ); + if( replacementStrategy ) + { + // TODO; Mark deleted, I guess... not implemented + } + else + { + dsl.delete( entitiesTable ) + .where( + identityColumnOf( entitiesTable ).eq( entityReference.identity().toString() ) + ) + .execute() + ; + String valueId = baseEntity.currentValueIdentity.toString(); + List<Table<Record>> mixinTables = getMixinTables( descriptor ); + List<Table<Record>> assocTables = getAssocationsTables( descriptor ); + mixinTables.forEach( table -> dsl.delete( table ).where( identityColumnOf( table ).eq( valueId ) ).execute() ); + assocTables.forEach( table -> dsl.delete( table ).where( identityColumnOf( table ).eq( valueId ) ).execute() ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java new file mode 100644 index 0000000..d89c058 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java @@ -0,0 +1,58 @@ +/* + * 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.polygene.entitystore.sql; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import javax.sql.DataSource; +import org.apache.polygene.api.injection.scope.Service; +import org.apache.polygene.api.injection.scope.Uses; +import org.apache.polygene.api.mixin.Mixins; +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.SQLDialect; +import org.jooq.conf.Settings; +import org.jooq.impl.DSL; +import org.jooq.impl.DefaultConfiguration; + +@Mixins( JooqDslContext.Mixin.class ) +public interface JooqDslContext extends DSLContext +{ + + class Mixin + implements InvocationHandler + { + private DSLContext dsl; + + public Mixin( @Service DataSource dataSource, @Uses Settings settings, @Uses SQLDialect dialect ) + { + Configuration configuration = new DefaultConfiguration() + .set( dataSource ) + .set( dialect ) + .set( settings ); + dsl = DSL.using( configuration ); + } + + @Override + public Object invoke( Object o, Method method, Object[] objects ) + throws Throwable + { + return method.invoke( dsl, objects ); // delegate all + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java new file mode 100644 index 0000000..80c26a2 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java @@ -0,0 +1,256 @@ +/* + * 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.polygene.entitystore.sql; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.common.QualifiedName; +import org.apache.polygene.api.entity.EntityDescriptor; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.spi.entity.ManyAssociationState; +import org.apache.polygene.spi.entity.NamedAssociationState; +import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState; +import org.jooq.Field; +import org.jooq.InsertSetMoreStep; +import org.jooq.InsertSetStep; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.UpdateSetMoreStep; +import org.jooq.impl.DSL; + +class MixinTable + implements TableFields +{ + + private final Table<Record> mixinTable; + private final Table<Record> mixinAssocsTable; + + private final JooqDslContext dsl; + private final Map<QualifiedName, Field<Object>> properties = new ConcurrentHashMap<>(); + private final Map<QualifiedName, Field<String>> associations = new ConcurrentHashMap<>(); + private final List<QualifiedName> manyAssociations = new CopyOnWriteArrayList<>(); + private final List<QualifiedName> namedAssociations = new CopyOnWriteArrayList<>(); + + private TypesTable types; + private final Class<?> mixinType; + + MixinTable( JooqDslContext dsl, Schema schema, TypesTable types, Class<?> mixinType, + EntityDescriptor descriptor ) + { + this.dsl = dsl; + this.types = types; + this.mixinType = mixinType; + mixinTable = types.tableFor( mixinType, descriptor ); + mixinAssocsTable = getAssocsTable( descriptor, schema ); + + descriptor.valueType().properties() + .filter( this::isThisMixin ) + .forEach( propDescriptor -> + { + QualifiedName propertyName = propDescriptor.qualifiedName(); + Field<Object> propertyField = types.fieldOf( propDescriptor ); + properties.put( propertyName, propertyField ); + } + ); + + descriptor.valueType().associations() + .filter( this::isThisMixin ) + .forEach( assocDescriptor -> + { + QualifiedName assocName = assocDescriptor.qualifiedName(); + Field<String> assocField = types.fieldOf( assocDescriptor ); + associations.put( assocName, assocField ); + } + ); + + descriptor.valueType().manyAssociations() + .filter( this::isThisMixin ) + .forEach( assocDescriptor -> manyAssociations.add( assocDescriptor.qualifiedName() ) ); + + descriptor.valueType().namedAssociations() + .filter( this::isThisMixin ) + .forEach( assocDescriptor -> namedAssociations.add( assocDescriptor.qualifiedName() ) ); + } + + void insertMixinState( DefaultEntityState state, String valueIdentity ) + { + InsertSetMoreStep<Record> primaryTable = + dsl.insertInto( mixinTable ) + .set( identityColumn, valueIdentity ) + .set( createdColumn, new Timestamp( System.currentTimeMillis() ) ); + + properties.forEach( ( propertyName, propertyField ) -> primaryTable.set( propertyField, state.propertyValueOf( propertyName ) ) ); + associations.forEach( ( assocName, assocField ) -> + { + EntityReference reference = state.associationValueOf( assocName ); + String identity = null; + if( reference != null ) + { + identity = reference.identity().toString(); + } + primaryTable.set( assocField, identity ); + } + ); + int result = primaryTable.execute(); + + if( mixinAssocsTable != null ) + { + insertManyAndNamedAssociations( state, valueIdentity ); + } + } + + private void insertManyAndNamedAssociations( DefaultEntityState state, String valueIdentity ) + { + manyAssociations.forEach( assocName -> + { + InsertSetStep<Record> assocsTable = dsl.insertInto( mixinAssocsTable ); + ManyAssociationState entityReferences = state.manyAssociationValueOf( assocName ); + int endCount = entityReferences.count(); + int counter = 0; + for( EntityReference ref : entityReferences ) + { + InsertSetMoreStep<Record> set = assocsTable.set( identityColumn, valueIdentity ) + .set( nameColumn, assocName.name() ) + .set( indexColumn, "" + counter++ ) + .set( referenceColumn, ref == null ? null : ref.identity().toString() ); + if( ++counter < endCount ) + { + set.newRecord(); + } + } + InsertSetMoreStep<Record> assocs = assocsTable.set( Collections.emptyMap() ); + assocs.execute(); + } ); + + namedAssociations.forEach( assocName -> + { + InsertSetStep<Record> assocsTable = dsl.insertInto( mixinAssocsTable ); + NamedAssociationState entityReferences = state.namedAssociationValueOf( assocName ); + int count = entityReferences.count(); + for( String name : entityReferences ) + { + EntityReference ref = entityReferences.get( name ); + InsertSetMoreStep<Record> set = assocsTable.set( identityColumn, valueIdentity ) + .set( nameColumn, assocName.name() ) + .set( indexColumn, name ) + .set( referenceColumn, ref.identity().toString() ); + if( --count > 0 ) + { + set.newRecord(); + } + } + InsertSetMoreStep<Record> assocs = assocsTable.set( Collections.emptyMap() ); + assocs.execute(); + } ); + } + + Table<Record> associationsTable() + { + return mixinAssocsTable; + } + + private boolean isThisMixin( PropertyDescriptor descriptor ) + { + Class<?> declaringClass = declaredIn( descriptor ); + return mixinType.equals( declaringClass ); + } + + private boolean isThisMixin( AssociationDescriptor descriptor ) + { + Class<?> declaringClass = declaredIn( descriptor ); + return mixinType.equals( declaringClass ); + } + + private Class<?> declaredIn( PropertyDescriptor descriptor ) + { + AccessibleObject accessor = descriptor.accessor(); + if( accessor instanceof Method ) + { + return ( (Method) accessor ).getDeclaringClass(); + } + throw new UnsupportedOperationException( "Property declared as " + accessor.getClass() + " is not supported in this Entity Store yet." ); + } + + private Class<?> declaredIn( AssociationDescriptor descriptor ) + { + AccessibleObject accessor = descriptor.accessor(); + if( accessor instanceof Method ) + { + return ( (Method) accessor ).getDeclaringClass(); + } + throw new UnsupportedOperationException( "Property declared as " + accessor.getClass() + " is not supported in this Entity Store yet." ); + } + + void modifyMixinState( DefaultEntityState state, String valueId ) + { + UpdateSetMoreStep<Record> primaryTable = + dsl.update( mixinTable ) + .set( Collections.emptyMap() ); // empty map is a hack to get the right type returned from JOOQ. + + properties.forEach( ( propertyName, propertyField ) -> primaryTable.set( propertyField, state.propertyValueOf( propertyName ) ) ); + associations.forEach( ( assocName, assocField ) -> + { + EntityReference reference = state.associationValueOf( assocName ); + primaryTable.set( assocField, + reference == null ? null : reference.identity().toString() + ); + } + ); + int result = primaryTable.execute(); + + if( mixinAssocsTable != null ) + { + // Need to remove existing records. + dsl.delete( mixinAssocsTable ) + .where( identityColumn.eq( valueId ) ) + .execute(); + insertManyAndNamedAssociations( state, valueId ); + } + } + + private Table<Record> getAssocsTable( EntityDescriptor descriptor, Schema schema ) + { + if( descriptor.state().manyAssociations().count() > 0 + || descriptor.state().namedAssociations().count() > 0 ) + { + Name tableName = DSL.name( schema.getName(), mixinTable.getName() + ASSOCS_TABLE_POSTFIX ); + Table<Record> table = DSL.table( tableName ); + int result2 = dsl.createTableIfNotExists( table ) + .column( identityColumn ) + .column( nameColumn ) + .column( indexColumn ) + .column( referenceColumn ) + .execute(); + return table; + } + else + { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java new file mode 100644 index 0000000..e93d7a3 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java @@ -0,0 +1,73 @@ +/* + * 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.polygene.entitystore.sql; + +import org.apache.polygene.api.common.UseDefaults; +import org.apache.polygene.api.property.Property; +import org.apache.polygene.library.sql.common.SQLConfiguration; + +// START SNIPPET: config +public interface SqlEntityStoreConfiguration extends SQLConfiguration +{ + /** + * Name of the database schema to use. + * Ignored on SQL databases that don't support schemas. + */ + @UseDefaults( "POLYGENE" ) + @Override + Property<String> schemaName(); + + /** + * Name of the entities table. + * <p> + * This table contains the Identity and other metadata about each entity instance + * </p> + */ + @UseDefaults( "ENTITIES" ) + Property<String> entitiesTableName(); + + /** + * Name of the entity types table. + * <p> + * This table contains the metainfo about each type. Types are versioned according to + * application version, to support entity migration over time, and therefor there might + * be (but not necessarily) multiple tables for entity types that has evolved beyond + * what can be managed within a single table. + * </p> + */ + @UseDefaults( "TYPES" ) + Property<String> typesTableName(); + + /** + * Defines whether the database schema and table should be created if not already present. + */ + @UseDefaults( "true" ) + Property<Boolean> createIfMissing(); + + /** + * The SQL dialect that is being used. + * <p> + * Typically that is matching a supporting dialect in JOOQ. + * See {@link org.jooq.SQLDialect} for supported values. + * </p> + * @return The property with the dialect value. + */ + @UseDefaults( "" ) + Property<String> dialect(); +} +// END SNIPPET: config http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java new file mode 100644 index 0000000..5f17fd4 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java @@ -0,0 +1,367 @@ +/* + * 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.polygene.entitystore.sql; + +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.association.AssociationStateDescriptor; +import org.apache.polygene.api.common.QualifiedName; +import org.apache.polygene.api.entity.EntityDescriptor; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.identity.HasIdentity; +import org.apache.polygene.api.identity.IdentityGenerator; +import org.apache.polygene.api.injection.scope.Service; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.serialization.Serialization; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.ValueType; +import org.apache.polygene.api.usecase.Usecase; +import org.apache.polygene.spi.entity.EntityState; +import org.apache.polygene.spi.entity.EntityStatus; +import org.apache.polygene.spi.entitystore.DefaultEntityStoreUnitOfWork; +import org.apache.polygene.spi.entitystore.EntityNotFoundException; +import org.apache.polygene.spi.entitystore.EntityStore; +import org.apache.polygene.spi.entitystore.EntityStoreSPI; +import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork; +import org.apache.polygene.spi.entitystore.StateCommitter; +import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.SelectQuery; + +import static org.apache.polygene.api.entity.EntityReference.parseEntityReference; + +public class SqlEntityStoreMixin + implements EntityStore, EntityStoreSPI +{ + @This + private SqlTable sqlTable; + + @Service + private IdentityGenerator identityGenerator; + + @Service + private Serialization serialization; + + @Override + public EntityState newEntityState( EntityStoreUnitOfWork unitOfWork, EntityReference reference, EntityDescriptor entityDescriptor ) + { + return new DefaultEntityState( unitOfWork.currentTime(), reference, entityDescriptor ); + } + + @Override + public EntityState entityStateOf( EntityStoreUnitOfWork unitOfWork, ModuleDescriptor module, EntityReference reference ) + { + BaseEntity baseEntity = sqlTable.fetchBaseEntity( reference, module ); + SelectQuery<Record> selectQuery = sqlTable.createGetEntityQuery( baseEntity.type, reference ); + Result<Record> result = selectQuery.fetch(); + if( result.isEmpty() ) + { + throw new EntityNotFoundException( reference ); + } + return toEntityState( result, baseEntity, reference, module ); + } + + protected EntityState toEntityState( Result<Record> result, BaseEntity baseEntity, EntityReference reference, ModuleDescriptor module ) + { + AssociationStateDescriptor stateDescriptor = baseEntity.type.state(); + Map<QualifiedName, Object> properties = new HashMap<>(); + properties.put( HasIdentity.IDENTITY_STATE_NAME, baseEntity.identity ); + stateDescriptor.properties() + .filter( prop -> !HasIdentity.IDENTITY_STATE_NAME.equals( prop.qualifiedName() ) ) + .forEach( prop -> + { + QualifiedName qualifiedName = prop.qualifiedName(); + Object value = result.getValue( 0, qualifiedName.name() ); + value = amendValue( value, prop.valueType(), module ); + properties.put( qualifiedName, value ); + } ); + Map<QualifiedName, EntityReference> assocations = new HashMap<>(); + stateDescriptor.associations() + .forEach( assoc -> + { + QualifiedName qualifiedName = assoc.qualifiedName(); + String value = (String) result.getValue( 0, qualifiedName.name() ); + if( value != null ) + { + assocations.put( qualifiedName, parseEntityReference( value ) ); + } + } ); + Map<QualifiedName, List<EntityReference>> manyAssocs = new HashMap<>(); + Map<QualifiedName, Map<String, EntityReference>> namedAssocs = new HashMap<>(); + sqlTable.fetchAssociations( baseEntity, baseEntity.type, associationValue -> + { + if( stateDescriptor.hasManyAssociation( associationValue.name ) ) + { + addManyAssociation( stateDescriptor, manyAssocs, associationValue ); + } + else if( stateDescriptor.hasNamedAssociation( associationValue.name ) ) + { + addNamedAssociation( stateDescriptor, namedAssocs, associationValue ); + } + } ); + + return new DefaultEntityState( baseEntity.version, + baseEntity.modifedAt, + reference, + EntityStatus.LOADED, + baseEntity.type, + properties, + assocations, + manyAssocs, + namedAssocs ); + } + + private Object amendValue( Object value, ValueType type, ModuleDescriptor module ) + { + if( value == null ) + { + return null; + } + if( value.getClass().isPrimitive() ) + { + return value; + } + if( type.equals( ValueType.STRING ) + || type.equals( ValueType.INTEGER ) + || type.equals( ValueType.BOOLEAN ) + || type.equals( ValueType.DOUBLE ) + || type.equals( ValueType.IDENTITY ) + || type.equals( ValueType.LONG ) + || type.equals( ValueType.FLOAT ) + || type.equals( ValueType.BYTE ) + || type.equals( ValueType.CHARACTER ) + || type.equals( ValueType.ENTITY_REFERENCE ) + || type.equals( ValueType.SHORT ) + || type.equals( ValueType.BIG_INTEGER ) + || type.equals( ValueType.BIG_DECIMAL ) + ) + { + return value; + } + if( type.equals( ValueType.INSTANT ) ) // Instant type contains timezone (why?), and we promise to always return in UTC (or is that just bad testcases, and that we actually promise to return original instant timezone?). + { + if( value instanceof Instant ) + { + return Instant.ofEpochMilli( ( (Instant) value ).toEpochMilli() ); + } + if( value instanceof OffsetDateTime ) + { + return Instant.ofEpochMilli( ( (OffsetDateTime) value ).toInstant().toEpochMilli() ); + } + if( value instanceof ZonedDateTime ) + { + return Instant.ofEpochMilli( ( (ZonedDateTime) value ).toInstant().toEpochMilli() ); + } + } + if( type.equals( ValueType.ZONED_DATE_TIME ) ) + { + if( value instanceof ZonedDateTime ) + { + return ( (ZonedDateTime) value ).withZoneSameInstant( ZoneOffset.UTC ); + } + if( value instanceof OffsetDateTime ) + { + return ( (OffsetDateTime) value ).toZonedDateTime().withZoneSameInstant( ZoneOffset.UTC ); + } + } + if( type.equals( ValueType.OFFSET_DATE_TIME ) ) + { + if( value instanceof OffsetDateTime ) + { + return ( (OffsetDateTime) value ).withOffsetSameInstant( ZoneOffset.UTC ); + } + if( value instanceof ZonedDateTime ) + { + return ( (ZonedDateTime) value ).toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ); + } + } + if( type.equals( ValueType.LOCAL_DATE_TIME ) ) + { + if( value instanceof Timestamp ) + { + return ( (Timestamp) value ).toLocalDateTime(); + } + } + if( type.equals( ValueType.PERIOD ) ) + { + if( value instanceof String ) + { + return Period.parse( (String) value ); + } + } + if( type.equals( ValueType.DURATION ) ) + { + if( value instanceof String ) + { + return Duration.parse( (String) value ); + } + } + if( type.equals( ValueType.LOCAL_DATE ) ) + { + if( value instanceof java.sql.Date ) + { + return ( (java.sql.Date) value ).toLocalDate(); + } + } + if( type.equals( ValueType.LOCAL_TIME ) ) + { + if( value instanceof java.sql.Time ) + { + return ( (java.sql.Time) value ).toLocalTime(); + } + } + // otherwise, we deal with a serialized value. + return serialization.deserialize( module, type, (String) value ); + } + + private void addNamedAssociation( AssociationStateDescriptor stateDescriptor, Map<QualifiedName, Map<String, EntityReference>> namedAssocs, AssociationValue associationValue ) + { + AssociationDescriptor descriptor = stateDescriptor.getNamedAssociationByName( associationValue.name.name() ); + QualifiedName qualifiedName = descriptor.qualifiedName(); + Map<String, EntityReference> map = namedAssocs.computeIfAbsent( qualifiedName, k -> new HashMap<>() ); + map.put( associationValue.position, parseEntityReference( associationValue.reference ) ); + } + + private void addManyAssociation( AssociationStateDescriptor stateDescriptor, Map<QualifiedName, List<EntityReference>> manyAssocs, AssociationValue associationValue ) + { + AssociationDescriptor descriptor = stateDescriptor.getManyAssociationByName( associationValue.name.name() ); + QualifiedName qualifiedName = descriptor.qualifiedName(); + List<EntityReference> list = manyAssocs.computeIfAbsent( qualifiedName, k -> new ArrayList<>() ); + String reference = associationValue.reference; + list.add( reference == null ? null : parseEntityReference( reference ) ); + } + + @Override + public String versionOf( EntityStoreUnitOfWork unitOfWork, EntityReference reference ) + { + BaseEntity baseEntity = sqlTable.fetchBaseEntity( reference, unitOfWork.module() ); + return baseEntity.version; + } + + @Override + public StateCommitter applyChanges( EntityStoreUnitOfWork unitOfWork, Iterable<EntityState> state ) + { + return new JooqStateCommitter( unitOfWork, state, sqlTable.jooqDslContext() ); + } + + @Override + public EntityStoreUnitOfWork newUnitOfWork( ModuleDescriptor module, Usecase usecase, Instant currentTime ) + { + return new DefaultEntityStoreUnitOfWork( module, + this, + identityGenerator.generate( SqlEntityStoreService.class ), + usecase, + currentTime + ); + } + + @Override + public Stream<EntityState> entityStates( ModuleDescriptor module ) + { + Stream<? extends EntityDescriptor> entityTypes = module.entityComposites(); + return entityTypes + .flatMap( type -> sqlTable.fetchAll( type, module ) ) + .map( baseEntity -> + { + EntityReference reference = EntityReference.entityReferenceFor( baseEntity.identity ); + SelectQuery<Record> selectQuery = sqlTable.createGetEntityQuery( baseEntity.type, reference ); + Result<Record> result = selectQuery.fetch(); + return toEntityState( result, baseEntity, reference, module ); + } ); + } + + private class JooqStateCommitter + implements StateCommitter + { + private final EntityStoreUnitOfWork unitOfWork; + private final Iterable<EntityState> states; + private final JooqDslContext dslContext; + private final ModuleDescriptor module; + + JooqStateCommitter( EntityStoreUnitOfWork unitOfWork, Iterable<EntityState> states, JooqDslContext dslContext ) + { + this.unitOfWork = unitOfWork; + this.states = states; + this.dslContext = dslContext; + this.module = unitOfWork.module(); + } + + private void newState( DefaultEntityState state, EntityStoreUnitOfWork unitOfWork ) + { + EntityReference ref = state.entityReference(); + EntityDescriptor descriptor = state.entityDescriptor(); + sqlTable.createNewBaseEntity( ref, descriptor, this.unitOfWork ); + sqlTable.insertEntity( state, sqlTable.fetchBaseEntity( ref, module ), unitOfWork ); + } + + private void updateState( DefaultEntityState state, EntityStoreUnitOfWork unitOfWork ) + { + EntityDescriptor descriptor = state.entityDescriptor(); + BaseEntity baseEntity = sqlTable.fetchBaseEntity( state.entityReference(), descriptor.module() ); + sqlTable.updateEntity( state, baseEntity, unitOfWork ); + } + + private void removeState( DefaultEntityState state ) + { + EntityReference reference = state.entityReference(); + EntityDescriptor descriptor = state.entityDescriptor(); + sqlTable.removeEntity( reference, descriptor ); + } + + @Override + public void commit() + { + dslContext.transaction( configuration -> + { + for( EntityState es : this.states ) + { + DefaultEntityState state = (DefaultEntityState) es; + if( state.status() == EntityStatus.NEW ) + { + newState( state, unitOfWork ); + } + if( state.status() == EntityStatus.UPDATED ) + { + updateState( state, unitOfWork ); + } + if( state.status() == EntityStatus.REMOVED ) + { + removeState( state ); + } + } + } ); + } + + @Override + public void cancel() + { + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java new file mode 100644 index 0000000..cf401d6 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java @@ -0,0 +1,36 @@ +/* + * 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.polygene.entitystore.sql; + +import org.apache.polygene.api.concern.Concerns; +import org.apache.polygene.api.configuration.Configuration; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.spi.entitystore.ConcurrentModificationCheckConcern; +import org.apache.polygene.spi.entitystore.EntityStateVersions; +import org.apache.polygene.spi.entitystore.EntityStore; +import org.apache.polygene.spi.entitystore.StateChangeNotificationConcern; + +/** + * SQL EntityStore service. + */ +@Concerns( { StateChangeNotificationConcern.class, ConcurrentModificationCheckConcern.class } ) +@Mixins( { SqlEntityStoreMixin.class } ) +public interface SqlEntityStoreService + extends EntityStore, EntityStateVersions, Configuration, SqlTable +{ +}
