http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java new file mode 100644 index 0000000..6537782 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java @@ -0,0 +1,271 @@ +/* + * 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.util.function.Consumer; +import java.util.stream.Stream; +import javax.sql.DataSource; +import org.apache.polygene.api.PolygeneAPI; +import org.apache.polygene.api.composite.TransientBuilderFactory; +import org.apache.polygene.api.configuration.Configuration; +import org.apache.polygene.api.entity.EntityDescriptor; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.injection.scope.Service; +import org.apache.polygene.api.injection.scope.Structure; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.injection.scope.Uses; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.object.ObjectFactory; +import org.apache.polygene.api.service.ServiceActivation; +import org.apache.polygene.api.service.ServiceDescriptor; +import org.apache.polygene.api.structure.Application; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork; +import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState; +import org.jooq.Record; +import org.jooq.SQLDialect; +import org.jooq.Schema; +import org.jooq.SelectQuery; +import org.jooq.conf.RenderNameStyle; +import org.jooq.conf.Settings; +import org.jooq.impl.DSL; + +/** + * This class handles all the Jooq interactions. + * <p> + * <p> + * <p> + * <h1>Tables</h1> + * <h2>Types Table</h2> + * <ul> + * <li>identity</li> + * <li>table_name</li> + * <li>created_at</li> + * <li>modified_at</li> + * </ul> + * <h2>Entities Table</h2> + * <ul> + * <li>identity</li> + * <li>app_version</li> + * <li>value_id</li> + * <li>version</li> + * <li>type</li> + * <li>modified_at</li> + * <li>created_at</li> + * </ul> + * <h2>Mixin Tables</h2> + * <p> + * Each Mixin is stored in its own table. Only the following column is always present; + * <ul> + * <li>identity - this is not entity identity but the UUID of the value_id in the Entities Table above.</li> + * </ul> + * </p> + * <p> + * Each Property of the Mixin (as defined by QualifiedName of the Property, will reside in its own column. + * All values in columns are (for now) serialized using a ValueSerialization service. + * </p> + * <p> + * Associations also has their own columns in the table, with the EntityReference.identity() stored in them. + * </p> + * <p> + * ManyAssociations and NamedAssociations are stored in a separate table, named <mixintable>_ASSOCS, see below. + * </p> + * <h2>Mixin_ASSOCS Table</h2> + * <ul> + * <li>identity - the value_id of the mixin value</li> + * <li>name - the name of the ManyAssociation or NamedAssociation</li> + * <li>position - for NamedAssociation this is the 'name' (i.e key) and for ManyAssociation this is the index into the list.</li> + * <li>reference - EntityReference.identity of that association</li> + * </ul> + */ +@Mixins( SqlTable.Mixin.class ) +public interface SqlTable extends ServiceActivation +{ + BaseEntity fetchBaseEntity( EntityReference reference, ModuleDescriptor module ); + + SelectQuery<Record> createGetEntityQuery( EntityDescriptor descriptor, EntityReference reference ); + + void fetchAssociations( BaseEntity entity, EntityDescriptor descriptor, Consumer<AssociationValue> consume ); + + void createNewBaseEntity( EntityReference ref, EntityDescriptor descriptor, EntityStoreUnitOfWork unitOfWork ); + + void insertEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork ); + + void updateEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork ); + + JooqDslContext jooqDslContext(); + + void removeEntity( EntityReference entityReference, EntityDescriptor descriptor ); + + Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module ); + + class Mixin + implements SqlTable, TableFields, ServiceActivation + { + @Structure + private Application application; + + @Structure + private TransientBuilderFactory tbf; + + @This + private Configuration<SqlEntityStoreConfiguration> configuration; + + @Service + private DataSource datasource; + + @Uses + private ServiceDescriptor serviceDescriptor; + + private EntitiesTable entitiesTable; + + private TypesTable types; + private JooqDslContext dsl; + + @Override + public BaseEntity fetchBaseEntity( EntityReference reference, ModuleDescriptor module ) + { + return entitiesTable.fetchEntity( reference, module ); + } + + @Override + public Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module ) + { + return entitiesTable.fetchAll( type, module ); + } + + @Override + public SelectQuery<Record> createGetEntityQuery( EntityDescriptor descriptor, EntityReference reference ) + { + return entitiesTable.createGetEntityQuery( descriptor, reference ); + } + + @Override + public void fetchAssociations( BaseEntity entity, EntityDescriptor descriptor, Consumer<AssociationValue> consume ) + { + entitiesTable.fetchAssociations( entity, descriptor, consume ); + } + + @Override + public void createNewBaseEntity( EntityReference ref, EntityDescriptor descriptor, EntityStoreUnitOfWork unitOfWork ) + { + entitiesTable.createNewBaseEntity( ref, descriptor, unitOfWork ); + } + + @Override + public void insertEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork ) + { + entitiesTable.insertEntity( state, baseEntity ); + } + + @Override + public void updateEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork ) + { + entitiesTable.modifyEntity( state, baseEntity, unitOfWork ); + } + + @Override + public JooqDslContext jooqDslContext() + { + return dsl; + } + + @Override + public void removeEntity( EntityReference reference, EntityDescriptor descriptor ) + { + entitiesTable.removeEntity( reference, descriptor ); + } + + @Override + public void activateService() + throws Exception + { + SqlEntityStoreConfiguration config = this.configuration.get(); + SQLDialect dialect = getSqlDialect( config ); + + Settings settings = serviceDescriptor + .metaInfo( Settings.class ) + .withRenderNameStyle( RenderNameStyle.QUOTED ); + dsl = tbf.newTransient( JooqDslContext.class, settings, dialect ); + + String schemaName = config.schemaName().get(); + String typesTableName = config.typesTableName().get(); + String entitiesTableName = config.entitiesTableName().get(); + Schema schema = DSL.schema( DSL.name( schemaName ) ); + types = new TypesTable( dsl, schema, dialect, typesTableName ); + entitiesTable = new EntitiesTable( dsl, schema, types, application.version(), entitiesTableName ); + + // Eventually create schema + if( config.createIfMissing().get() ) + { + if( !dialect.equals( SQLDialect.SQLITE ) + && dsl.meta().getSchemas().stream().noneMatch( s -> schema.getName().equalsIgnoreCase( s.getName() ) ) ) + { + dsl.createSchema( schema ).execute(); + } + + dsl.createTableIfNotExists( DSL.name( schemaName, typesTableName ) ) + .column( identityColumn ) + .column( tableNameColumn ) + .column( createdColumn ) + .column( modifiedColumn ) + .execute(); + + dsl.createTableIfNotExists( DSL.name( schemaName, entitiesTableName ) ) + .column( identityColumn ) + .column( applicationVersionColumn ) + .column( valueIdentityColumn ) + .column( versionColumn ) + .column( typeNameColumn ) + .column( modifiedColumn ) + .column( createdColumn ) + .execute(); + } + datasource.getConnection().commit(); + } + + @Override + public void passivateService() + throws Exception + { + + } + + private SQLDialect getSqlDialect( SqlEntityStoreConfiguration config ) + { + SQLDialect dialect = null; + String dialectString = config.dialect().get(); + if( dialectString.length() == 0 ) + { + dialect = SQLDialect.DEFAULT; + } + else + { + try + { + dialect = SQLDialect.valueOf( dialectString ); + } + catch( IllegalArgumentException e ) + { + throw new IllegalArgumentException( "Invalid SQLDialect: '" + dialectString + "'" ); + } + } + return dialect; + } + } +}
http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java new file mode 100644 index 0000000..21de44d --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java @@ -0,0 +1,156 @@ +/* + * 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.math.BigDecimal; +import java.math.BigInteger; +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.DefaultDataType; +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.REAL; + } + 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( Byte.class.isAssignableFrom( propertyType ) ) + { + return (DataType<T>) SQLDataType.INTEGER; + } + if( BigDecimal.class.isAssignableFrom( propertyType ) ) + { + return (DataType<T>) SQLDataType.DECIMAL; + } + if( BigInteger.class.isAssignableFrom( propertyType ) ) + { + return (DataType<T>) SQLDataType.DECIMAL(50, 0); + } + 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.REAL; + } + 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-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java new file mode 100644 index 0000000..cfa9d99 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java @@ -0,0 +1,69 @@ +/* + * 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 org.jooq.Field; + +import static org.apache.polygene.entitystore.sql.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-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java new file mode 100644 index 0000000..d04bc95 --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java @@ -0,0 +1,189 @@ +/* + * 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.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-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java new file mode 100644 index 0000000..b1f660f --- /dev/null +++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java @@ -0,0 +1,77 @@ +/* + * 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.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.sql.JooqDslContext; +import org.apache.polygene.entitystore.sql.SqlEntityStoreConfiguration; +import org.apache.polygene.entitystore.sql.SqlEntityStoreService; +import org.jooq.SQLDialect; +import org.jooq.conf.RenderNameStyle; +import org.jooq.conf.Settings; + +/** + * JOOQ EntityStore assembly. + */ +@SuppressWarnings( "WeakerAccess" ) +public class SqlEntityStoreAssembler extends Assemblers.VisibilityIdentityConfig<SqlEntityStoreAssembler> + implements Assembler +{ + public static final Identity DEFAULT_ENTITYSTORE_IDENTITY = StringIdentity.identityOf( "entitystore-sql" ); + + @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( SqlEntityStoreService.class ) + .identifiedBy( identity ) + .visibleIn( visibility() ) + .instantiateOnStartup() + .setMetaInfo( settings ); + + if( hasConfig() ) + { + configModule().entities( SqlEntityStoreConfiguration.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-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java b/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java new file mode 100644 index 0000000..d8f0d59 --- /dev/null +++ b/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java @@ -0,0 +1,88 @@ +/* + * 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.Visibility; +import org.apache.polygene.bootstrap.AssemblyException; +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.entitystore.sql.assembly.SqlEntityStoreAssembler; +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 SqlEntityStoreTest 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 SqlEntityStoreAssembler() + .withConfig( config, Visibility.layer ) + .identifiedBy( "sql-entitystore" ) + .assemble( module ); + // END SNIPPET: assembly + + SqlEntityStoreConfiguration jooqDefaults = config.forMixin( SqlEntityStoreConfiguration.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/settings.gradle ---------------------------------------------------------------------- diff --git a/settings.gradle b/settings.gradle index 78570dc..3a4067c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -61,13 +61,13 @@ include 'core:api', 'extensions:entitystore-hazelcast', 'extensions:entitystore-jclouds', 'extensions:entitystore-jdbm', - 'extensions:entitystore-jooq', 'extensions:entitystore-leveldb', 'extensions:entitystore-memory', 'extensions:entitystore-mongodb', 'extensions:entitystore-preferences', 'extensions:entitystore-redis', 'extensions:entitystore-riak', + 'extensions:entitystore-sql', 'extensions:entitystore-sqlkv', 'extensions:indexing-elasticsearch', 'extensions:indexing-rdf',
