From 8d3b4a04072b5e21ba646ea30f2467912be55836 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 1 Mar 2016 15:36:44 +1300 Subject: [PATCH] #304 - ElasticSearch integration part 1 / doc store integration --- pom.xml | 8 +- .../com/avaje/ebean/DocStoreQueueEntry.java | 94 ++++++ .../java/com/avaje/ebean/DocumentStore.java | 121 +++++++ .../java/com/avaje/ebean/EbeanServer.java | 5 + .../avaje/ebean/PersistenceIOException.java | 18 + src/main/java/com/avaje/ebean/Query.java | 16 +- .../java/com/avaje/ebean/Transaction.java | 25 ++ .../com/avaje/ebean/annotation/DocCode.java | 32 ++ .../avaje/ebean/annotation/DocMapping.java | 44 +++ .../avaje/ebean/annotation/DocProperty.java | 39 +++ .../avaje/ebean/annotation/DocSortable.java | 34 ++ .../com/avaje/ebean/annotation/DocStore.java | 88 +++++ .../ebean/annotation/DocStoreEmbedded.java | 37 ++ .../avaje/ebean/annotation/DocStoreEvent.java | 38 +++ .../avaje/ebean/bean/EntityBeanIntercept.java | 31 +- .../avaje/ebean/bean/PersistenceContext.java | 4 +- .../avaje/ebean/config/ClassLoadConfig.java | 10 + .../avaje/ebean/config/DocStoreConfig.java | 138 ++++++++ .../com/avaje/ebean/config/ServerConfig.java | 52 ++- .../model/build/ModelBuildBeanVisitor.java | 2 +- .../com/avaje/ebean/plugin/BeanDocType.java | 72 ++++ .../java/com/avaje/ebean/plugin/BeanType.java | 99 +++++- .../avaje/ebean/plugin/ExpressionPath.java | 20 ++ .../java/com/avaje/ebean/plugin/Property.java | 22 ++ .../com/avaje/ebean/plugin/SpiServer.java | 4 + .../avaje/ebean/text/json/JsonBeanReader.java | 35 ++ .../avaje/ebean/text/json/JsonContext.java | 10 + .../ebean/text/json/JsonReadOptions.java | 43 ++- .../com/avaje/ebean/text/json/JsonScalar.java | 6 +- .../ebeaninternal/api/LoadManyBuffer.java | 1 + .../ebeaninternal/api/LoadManyRequest.java | 2 +- .../ebeaninternal/api/ScopedTransaction.java | 21 ++ .../ebeaninternal/api/SpiEbeanServer.java | 5 + .../ebeaninternal/api/SpiExpression.java | 8 + .../com/avaje/ebeaninternal/api/SpiQuery.java | 35 +- .../ebeaninternal/api/SpiTransaction.java | 21 +- .../ebeaninternal/api/TransactionEvent.java | 13 + .../server/core/BeanRequest.java | 22 +- .../server/core/DefaultServer.java | 73 ++-- .../server/core/InternalConfiguration.java | 127 +++---- .../server/core/OrmQueryRequest.java | 11 +- .../server/core/PersistRequestBean.java | 136 +++++++- .../server/core/SpiOrmQueryRequest.java | 5 + .../server/deploy/BeanDescriptor.java | 224 ++++++++++++- .../server/deploy/BeanDescriptorJsonHelp.java | 78 +++-- .../server/deploy/BeanDescriptorManager.java | 31 +- .../server/deploy/BeanDescriptorMap.java | 9 + .../server/deploy/BeanFkeyProperty.java | 5 + .../server/deploy/BeanProperty.java | 71 +++- .../server/deploy/BeanPropertyAssoc.java | 69 +++- .../server/deploy/BeanPropertyAssocMany.java | 50 ++- .../deploy/DeployDocPropertyOptions.java | 73 ++++ .../deploy/meta/DeployBeanDescriptor.java | 105 +++++- .../deploy/meta/DeployBeanProperty.java | 24 ++ .../deploy/meta/DeployBeanPropertyAssoc.java | 17 +- .../server/deploy/parse/AnnotationClass.java | 6 + .../server/deploy/parse/AnnotationFields.java | 22 +- .../server/el/ElPropertyChain.java | 5 + .../server/el/ElPropertyValue.java | 3 +- .../expression/AllEqualsExpression.java | 17 + .../server/expression/BetweenExpression.java | 7 + .../expression/BetweenPropertyExpression.java | 11 + .../CaseInsensitiveEqualExpression.java | 17 + .../expression/DefaultExampleExpression.java | 12 + .../expression/DefaultExpressionFactory.java | 4 +- .../expression/DefaultExpressionList.java | 50 ++- .../expression/ElasticExpressionContext.java | 303 +++++++++++++++++ ...ession.java => ExistsQueryExpression.java} | 20 +- .../server/expression/IdExpression.java | 7 + .../server/expression/IdInExpression.java | 6 + .../server/expression/InExpression.java | 6 + .../server/expression/InQueryExpression.java | 8 +- .../server/expression/JsonPathExpression.java | 13 + .../server/expression/JunctionExpression.java | 18 +- .../server/expression/LikeExpression.java | 32 ++ .../server/expression/LogicExpression.java | 12 + .../server/expression/NoopExpression.java | 6 + .../server/expression/NotExpression.java | 9 + .../server/expression/NullExpression.java | 7 + .../ebeaninternal/server/expression/Op.java | 31 +- .../server/expression/RawExpression.java | 7 + .../server/expression/SimpleExpression.java | 12 + .../UnsupportedDocStoreExpression.java | 7 + .../server/loadcontext/DLoadBeanContext.java | 2 +- .../server/loadcontext/DLoadContext.java | 36 +- .../server/loadcontext/DLoadManyContext.java | 7 +- .../server/persist/dml/DmlHandler.java | 47 +-- .../BindablePropertyUpdateGenerated.java | 16 +- .../persist/dmlbind/BindableRequest.java | 2 +- .../ebeaninternal/server/query/CQuery.java | 2 +- .../server/query/SqlTreeBuilder.java | 2 +- .../server/query/SqlTreeNodeBean.java | 2 - .../server/querydefn/DefaultOrmQuery.java | 133 ++++++-- .../server/querydefn/OrmQueryDetail.java | 66 ++++ .../server/text/json/DJsonBeanReader.java | 51 +++ .../server/text/json/DJsonContext.java | 20 +- .../server/text/json/ReadJson.java | 106 +++++- .../AutoCommitTransactionManager.java | 9 +- .../DefaultPersistenceContext.java | 6 +- .../server/transaction/DeleteByIdMap.java | 29 +- .../ExplicitTransactionManager.java | 7 +- .../server/transaction/JdbcTransaction.java | 28 ++ .../transaction/PostCommitProcessing.java | 56 +++- .../transaction/TransactionManager.java | 29 +- .../type/CtCompoundPropertyElAdapter.java | 193 +++++------ .../server/type/DefaultTypeManager.java | 2 +- .../server/type/EnumToDbIntegerMap.java | 8 +- .../server/type/EnumToDbStringMap.java | 4 +- .../server/type/EnumToDbValueMap.java | 16 +- .../ebeaninternal/server/type/ScalarType.java | 6 + .../server/type/ScalarTypeBaseDate.java | 6 + .../server/type/ScalarTypeBaseDateTime.java | 6 + .../server/type/ScalarTypeBaseVarchar.java | 7 + .../server/type/ScalarTypeBigDecimal.java | 6 + .../server/type/ScalarTypeBoolean.java | 6 + .../server/type/ScalarTypeByte.java | 6 + .../server/type/ScalarTypeBytesBase.java | 6 + .../server/type/ScalarTypeBytesEncrypted.java | 6 + .../server/type/ScalarTypeDayOfWeek.java | 2 +- .../server/type/ScalarTypeDouble.java | 7 + .../server/type/ScalarTypeDuration.java | 7 + .../type/ScalarTypeEncryptedWrapper.java | 7 + .../server/type/ScalarTypeEnumStandard.java | 6 + .../type/ScalarTypeEnumWithMapping.java | 7 + .../server/type/ScalarTypeFile.java | 6 + .../server/type/ScalarTypeFloat.java | 7 + .../server/type/ScalarTypeInteger.java | 7 + .../server/type/ScalarTypeJodaLocalTime.java | 6 + .../server/type/ScalarTypeJsonMap.java | 6 + .../server/type/ScalarTypeJsonNode.java | 6 + .../server/type/ScalarTypeLocalTime.java | 7 + .../server/type/ScalarTypeLong.java | 7 + .../server/type/ScalarTypeMathBigInteger.java | 6 + .../server/type/ScalarTypeMonth.java | 2 +- .../server/type/ScalarTypeMonthDay.java | 5 + .../server/type/ScalarTypePostgresHstore.java | 5 + .../server/type/ScalarTypeShort.java | 7 + .../server/type/ScalarTypeString.java | 6 + .../server/type/ScalarTypeTime.java | 6 + .../server/type/ScalarTypeUUIDBinary.java | 5 + .../server/type/ScalarTypeUUIDNative.java | 5 + .../server/type/ScalarTypeWrapper.java | 6 + .../server/type/ScalarTypeYear.java | 7 + .../docstore/api/DocStoreBeanAdapter.java | 116 +++++++ .../docstore/api/DocStoreFactory.java | 22 ++ .../docstore/api/DocStoreIntegration.java | 20 ++ .../docstore/api/DocStoreQueryUpdate.java | 23 ++ .../docstore/api/DocStoreUpdate.java | 19 ++ .../docstore/api/DocStoreUpdateContext.java | 11 + .../docstore/api/DocStoreUpdateProcessor.java | 34 ++ .../docstore/api/DocStoreUpdates.java | 115 +++++++ .../api/DocumentNotFoundException.java | 15 + .../api/mapping/DocMappingBuilder.java | 143 ++++++++ .../api/mapping/DocPropertyAdapter.java | 47 +++ .../api/mapping/DocPropertyMapping.java | 130 ++++++++ .../api/mapping/DocPropertyOptions.java | 138 ++++++++ .../docstore/api/mapping/DocPropertyType.java | 78 +++++ .../api/mapping/DocPropertyVisitor.java | 43 +++ .../docstore/api/mapping/DocumentMapping.java | 103 ++++++ .../api/support/DocStoreBeanBaseAdapter.java | 315 ++++++++++++++++++ .../api/support/DocStoreDeleteEvent.java | 39 +++ .../support/DocStoreEmbeddedInvalidation.java | 23 ++ ...ocStoreEmbeddedInvalidationProperties.java | 30 ++ .../api/support/DocStoreIndexEvent.java | 42 +++ .../docstore/api/support/DocStructure.java | 96 ++++++ .../docstore/none/NoneDocStore.java | 82 +++++ .../none/NoneDocStoreBeanAdapter.java | 49 +++ .../docstore/none/NoneDocStoreFactory.java | 39 +++ .../none/NoneDocStoreUpdateProcessor.java | 24 ++ .../com/avaje/ebean/TestFilterWithEnum.java | 14 +- .../ebean/bean/EntityBeanInterceptTest.java | 35 ++ .../ebean/config/DocStoreConfigTest.java | 33 ++ .../com/avaje/ebean/plugin/BeanTypeTest.java | 192 +++++++++++ .../ebean/plugin/ExpressionPathTest.java | 79 +++++ .../com/avaje/ebean/plugin/PropertyTest.java | 56 ++++ .../com/avaje/ebean/plugin/SpiServerTest.java | 7 +- .../ebean/text/json/JsonBeanReaderTest.java | 88 +++++ .../ebean/text/json/JsonContextTest.java | 63 +++- .../text/json/JsonWriteOptionsTests.java | 1 - .../ebeaninternal/api/TDSpiEbeanServer.java | 13 +- .../server/deploy/BeanDescriptorTest.java | 7 +- .../BeanDescriptor_documentMappingTest.java | 85 +++++ .../deploy/BeanDescriptor_propertiesTest.java | 23 ++ ...eanDescriptor_whenCreatedPropertyTest.java | 8 +- .../server/expression/BaseElasticTest.java | 24 ++ .../DefaultExampleExpressionTest.java | 6 +- ...st.java => ExistsQueryExpressionTest.java} | 6 +- .../SimpleExpressionElasticTest.java | 27 ++ .../querydefn/DefaultOrmQueryElasticTest.java | 88 +++++ .../server/querydefn/TestQueryLanguage.java | 9 +- .../server/text/json/WriteJsonDirtyTest.java | 55 +++ .../server/text/json/WriteJsonTest.java | 5 +- .../server/type/TestEnumToBeanType.java | 6 +- .../support/DocStoreBeanBaseAdapterTest.java | 68 ++++ .../api/support/DocStoreDeleteEventTest.java | 65 ++++ .../api/support/DocStoreIndexEventTest.java | 68 ++++ .../tests/batchload/TestLoadOnDirty.java | 11 + .../com/avaje/tests/model/basic/Contact.java | 4 + .../com/avaje/tests/model/basic/Country.java | 2 + .../com/avaje/tests/model/basic/Customer.java | 5 + .../com/avaje/tests/model/basic/Order.java | 27 +- .../avaje/tests/model/basic/OrderDetail.java | 3 + .../com/avaje/tests/model/basic/Product.java | 148 ++++---- .../query/orderby/TestOrderByWithMany.java | 2 +- .../query/other/TestManyLazyLoadingQuery.java | 2 +- .../json/TestJsonBeanDescriptorParse.java | 2 +- .../update/TestUpdateAllLoadedProperties.java | 7 +- src/test/resources/junk.json | 36 +- src/test/resources/logback-test.xml | 10 +- 209 files changed, 6736 insertions(+), 573 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/DocStoreQueueEntry.java create mode 100644 src/main/java/com/avaje/ebean/DocumentStore.java create mode 100644 src/main/java/com/avaje/ebean/PersistenceIOException.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocCode.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocMapping.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocProperty.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocSortable.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocStore.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java create mode 100644 src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java create mode 100644 src/main/java/com/avaje/ebean/config/DocStoreConfig.java create mode 100644 src/main/java/com/avaje/ebean/plugin/BeanDocType.java create mode 100644 src/main/java/com/avaje/ebean/plugin/ExpressionPath.java create mode 100644 src/main/java/com/avaje/ebean/plugin/Property.java create mode 100644 src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java rename src/main/java/com/avaje/ebeaninternal/server/expression/{ExistsExpression.java => ExistsQueryExpression.java} (82%) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreBeanAdapter.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java create mode 100644 src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java create mode 100644 src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java create mode 100644 src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java create mode 100644 src/test/java/com/avaje/ebean/plugin/PropertyTest.java create mode 100644 src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java rename src/test/java/com/avaje/ebeaninternal/server/expression/{ExistsExpressionTest.java => ExistsQueryExpressionTest.java} (90%) create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java create mode 100644 src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java create mode 100644 src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEventTest.java create mode 100644 src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEventTest.java diff --git a/pom.xml b/pom.xml index f204fe790..dbc85a939 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ com.fasterxml.jackson.core jackson-databind - 2.5.3 + 2.6.5 provided @@ -81,7 +81,7 @@ com.fasterxml.jackson.core jackson-core - 2.5.3 + 2.6.5 @@ -138,8 +138,8 @@ com.squareup.okhttp okhttp - 2.1.0 - test + 2.4.0 + provided diff --git a/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java b/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java new file mode 100644 index 000000000..95fb94776 --- /dev/null +++ b/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java @@ -0,0 +1,94 @@ +package com.avaje.ebean; + +/** + * Bean holding the details to update the document store. + */ +public final class DocStoreQueueEntry { + + /** + * Action to either update or delete a document from the index. + */ + public enum Action { + + /** + * Action is to update a document in the doc store. + */ + INDEX(1), + + /** + * Action is to delete a document from the doc store.. + */ + DELETE(2), + + /** + * An update is required based on a change to a nested/embedded object at a given path. + */ + NESTED(3); + + int value; + + Action(int value) { + this.value = value; + } + + /** + * Return the value associated with this action type. + */ + public int getValue() { + return value; + } + } + + final Action type; + + final String queueId; + + final String path; + + final Object beanId; + + /** + * Construct for an INDEX or DELETE action. + */ + public DocStoreQueueEntry(Action type, String queueId, Object beanId) { + this(type, queueId, null, beanId); + } + + /** + * Construct for an NESTED/embedded path invalidation action. + */ + public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) { + this.type = type; + this.queueId = queueId; + this.path = path; + this.beanId = beanId; + } + + /** + * Return the event type. + */ + public Action getType() { + return type; + } + + /** + * Return the associate queueId. + */ + public String getQueueId() { + return queueId; + } + + /** + * Return the path if this is a nested update. + */ + public String getPath() { + return path; + } + + /** + * Return the bean id (which matches the document id). + */ + public Object getBeanId() { + return beanId; + } +} diff --git a/src/main/java/com/avaje/ebean/DocumentStore.java b/src/main/java/com/avaje/ebean/DocumentStore.java new file mode 100644 index 000000000..ed7e63838 --- /dev/null +++ b/src/main/java/com/avaje/ebean/DocumentStore.java @@ -0,0 +1,121 @@ +package com.avaje.ebean; + +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +/** + * Document storage operations. + */ +public interface DocumentStore { + + /** + * Update the associated document store using the result of the query. + *

+ * This will execute the query against the database creating a document for each + * bean graph and sending this to the document store. + *

+ *

+ * Note that the select and fetch paths of the query is set for you to match the + * document structure needed based on @DocStore and @DocStoreEmbedded + * so what this query requires is the predicates only. + *

+ *

+ * This query will be executed using findEach so it is safe to use a query + * that will fetch a lot of beans. The default bulkBatchSize is used. + *

+ * + * @param query The query used to update the associated document store. + */ + void indexByQuery(Query query); + + /** + * Update the associated document store index using the result of the query additionally specifying a + * bulkBatchSize to use for sending the messages to ElasticSearch. + */ + void indexByQuery(Query query, int bulkBatchSize); + + /** + * Update the document store for all beans of this type. + *

+ * This is the same as indexByQuery where the query has no predicates and so fetches all rows. + */ + void indexAll(Class beanType); + + /** + * Return the bean by fetching it's content from the document store. + * If the document is not found null is returned. + */ + @Nullable + T getById(Class beanType, Object id); + + /** + * Execute the query against the document store returning the list. + */ + List findList(Query query); + + /** + * Execute the query against the document store returning the paged list. + *

+ * The query should have firstRow or maxRows set prior to calling this method. + *

+ */ + PagedList findPagedList(Query query); + + /** + * Execute the query against the document store with the expectation of a large set of results + * that are processed in a scrolling resultSet fashion. + *

+ * For example, with the ElasticSearch doc store this uses SCROLL. + *

+ */ + void findEach(Query query, QueryEachConsumer consumer); + + /** + * Process the queue entries sending updates to the document store or queuing them for later processing. + */ + long process(List queueEntries) throws IOException; + + /** + * Drop the index from the document store (similar to DDL drop table). + */ + void dropIndex(String indexName); + + /** + * Create an index given a mapping file as a resource in the classPath (similar to DDL create table). + * + * @param indexName the name of the new index + * @param alias the alias of the index + * @param mappingResource the path of the mapping file as a resource in the classpath + */ + void createIndex(String indexName, String alias, String mappingResource); + + /** + * Copy the index to a new index. + *

+ * This copy process does not use the database but instead will copy from the source index to a destination index. + *

+ * + * @param beanType The bean type of the source index + * @param newIndex The name of the index to copy to + * + * @return the number of documents copied to the new index + */ + long copyIndex(Class beanType, String newIndex); + + /** + * Copy entries from an index to a new index but limiting to documents that have been + * modified since the sinceEpochMillis time. + *

+ * To support this the document needs to have a @WhenModified property. + *

+ * + * @param beanType The bean type of the source index + * @param newIndex The name of the index to copy to + * + * @return the number of documents copied to the new index + */ + long copyIndex(Class beanType, String newIndex, long sinceEpochMillis); + +} diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index ae17c78a3..bce142ffb 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -1891,6 +1891,11 @@ public interface EbeanServer { */ JsonContext json(); + /** + * Return the Document store. + */ + DocumentStore docStore(); + /** * Publish a single bean given its type and id returning the resulting live bean. *

diff --git a/src/main/java/com/avaje/ebean/PersistenceIOException.java b/src/main/java/com/avaje/ebean/PersistenceIOException.java new file mode 100644 index 000000000..08c26556f --- /dev/null +++ b/src/main/java/com/avaje/ebean/PersistenceIOException.java @@ -0,0 +1,18 @@ +package com.avaje.ebean; + +import javax.persistence.PersistenceException; + +/** + * Captures and wraps IOException's occurring during ElasticSearch processing etc. + */ +public class PersistenceIOException extends PersistenceException { + + public PersistenceIOException(String msg, Exception cause) { + super(msg, cause); + } + + public PersistenceIOException(Exception cause) { + super(cause); + } + +} diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index 0db48945f..363f2c90e 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -274,7 +274,7 @@ import java.util.Set; * @param * the type of Entity bean this query will fetch. */ -public interface Query extends Serializable { +public interface Query { /** * Return the RawSql that was set to use for this query. @@ -1285,6 +1285,14 @@ public interface Query extends Serializable { */ Query setUseQueryCache(boolean useQueryCache); + /** + * Set to true if this query should execute against the doc store. + *

+ * When setting this you may also consider disabling lazy loading. + *

+ */ + Query setUseDocStore(boolean useDocStore); + /** * When set to true when you want the returned beans to be read only. */ @@ -1364,4 +1372,10 @@ public interface Query extends Serializable { *

*/ Set validate(); + + /** + * Return the query in JSON form for ElasticSearch doc store. + */ + String asElasticQuery(); + } diff --git a/src/main/java/com/avaje/ebean/Transaction.java b/src/main/java/com/avaje/ebean/Transaction.java index 67a553de4..549271cbe 100644 --- a/src/main/java/com/avaje/ebean/Transaction.java +++ b/src/main/java/com/avaje/ebean/Transaction.java @@ -1,5 +1,7 @@ package com.avaje.ebean; +import com.avaje.ebean.annotation.DocStoreEvent; +import com.avaje.ebean.config.DocStoreConfig; import com.avaje.ebean.config.PersistBatch; import javax.persistence.PersistenceException; @@ -81,6 +83,29 @@ public interface Transaction extends Closeable { */ boolean isActive(); + /** + * Set the behavior for document store updates on this transaction. + *

+ * For example, set the mode to DocStoreEvent.IGNORE for this transaction and + * then any changes via this transaction are not sent to the doc store. This + * would be used when doing large bulk inserts into the database and we want + * to control how that is sent to the document store. + *

+ */ + void setDocStoreUpdateMode(DocStoreEvent updateMode); + + /** + * Set the batch size to use for sending messages to the document store. + *

+ * You might set this if you know the changes in this transaction result in especially large or + * especially small payloads and want to adjust the batch size to match. + *

+ *

+ * Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()} + *

+ */ + void setDocStoreUpdateBatchSize(int batchSize); + /** * Explicitly turn off or on the cascading nature of save() and delete(). This * gives the developer exact control over what beans are saved and deleted diff --git a/src/main/java/com/avaje/ebean/annotation/DocCode.java b/src/main/java/com/avaje/ebean/annotation/DocCode.java new file mode 100644 index 000000000..582ba71f2 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocCode.java @@ -0,0 +1,32 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to indicate that a particular string property should be treated as a 'code' and not analysed for text searching. + *

+ * By default all Id properties and all Enum properties are treated as 'code' and not analysed. + *

+ */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocCode { + + /** + * Set to true to have the property additionally stored separately from _source. + */ + boolean store() default false; + + /** + * Set a boost value specific to this property. + */ + float boost() default 1; + + /** + * Set a value to use instead of null. + */ + String nullValue() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/annotation/DocMapping.java b/src/main/java/com/avaje/ebean/annotation/DocMapping.java new file mode 100644 index 000000000..e8bb77b70 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocMapping.java @@ -0,0 +1,44 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the entity type maps to a document store (like ElasticSearch). + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocMapping { + + /** + * The property name the mapping applies to. + */ + String name(); + + /** + * Set this to true to indicate that this property should be un-analysed. + */ + boolean code() default false; + + /** + * Set this to true to get an additional un-analysed 'raw' field to use for sorting etc. + */ + boolean sortable() default false; + + /** + * Set to true to have the property additionally stored separately from _source. + */ + boolean store() default false; + + /** + * Set a boost value specific to this property. + */ + float boost() default 1; + + /** + * Set a value to use instead of null. + */ + String nullValue() default ""; +} diff --git a/src/main/java/com/avaje/ebean/annotation/DocProperty.java b/src/main/java/com/avaje/ebean/annotation/DocProperty.java new file mode 100644 index 000000000..f6e5fe471 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocProperty.java @@ -0,0 +1,39 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the entity type maps to a document store (like ElasticSearch). + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocProperty { + + /** + * Set this to true to indicate that this property should be un-analysed. + */ + boolean code() default false; + + /** + * Set this to true to get an additional un-analysed 'raw' field to use for sorting etc. + */ + boolean sortable() default false; + + /** + * Set to true to have the property additionally stored separately from _source. + */ + boolean store() default false; + + /** + * Set a boost value specific to this property. + */ + float boost() default 1; + + /** + * Set a value to use instead of null. + */ + String nullValue() default ""; +} diff --git a/src/main/java/com/avaje/ebean/annotation/DocSortable.java b/src/main/java/com/avaje/ebean/annotation/DocSortable.java new file mode 100644 index 000000000..f0b87b450 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocSortable.java @@ -0,0 +1,34 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to indicate that a particular string property should support sorting. What this typically means is that + * for ElasticSearch an additional 'raw' field is added that stores the un-analysed value. This un-analysed value + * can be used for sorting etc and the original field used for text searching. + *

+ * For example, customer name and product name are good candidates for marking with @DocSortable. + *

+ */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocSortable { + + /** + * Set to true to have the property additionally stored separately from _source. + */ + boolean store() default false; + + /** + * Set a boost value specific to this property. + */ + float boost() default 1; + + /** + * Set a value to use instead of null. + */ + String nullValue() default ""; +} diff --git a/src/main/java/com/avaje/ebean/annotation/DocStore.java b/src/main/java/com/avaje/ebean/annotation/DocStore.java new file mode 100644 index 000000000..503abfe67 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocStore.java @@ -0,0 +1,88 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the entity type maps to a document store (like ElasticSearch). + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocStore { + + /** + * A unique Id used when queuing reindex events. + */ + String queueId() default ""; + + /** + * The ElasticSearch index name. If left unspecified the short name of the bean type is used. + */ + String indexName() default ""; + + /** + * The ElasticSearch index type. If left unspecified the short name of the bean type is used. + */ + String indexType() default ""; + + /** + * The number of shards this index should use. + */ + int shards() default 0; + + /** + * The number of replicas this index should use. + */ + int replicas() default 0; + + /** + * Additional mapping that can be defined on the properties. + */ + DocMapping[] mapping() default {}; + + /** + * Specify the behavior when bean Insert, Update, Delete events occur. + */ + DocStoreEvent persist() default DocStoreEvent.DEFAULT; + + /** + * Specify the behavior when bean Insert occurs. + */ + DocStoreEvent insert() default DocStoreEvent.DEFAULT; + + /** + * Specify the behavior when bean Update occurs. + */ + DocStoreEvent update() default DocStoreEvent.DEFAULT; + + /** + * Specify the behavior when bean Delete occurs. + */ + DocStoreEvent delete() default DocStoreEvent.DEFAULT; + + /** + * Specify to include only some properties in the doc store document. + *

+ * If this is left as default then all scalar properties are included, + * all @ManyToOne properties are included with just the nested id property + * and no @OneToMany properties are included. + *

+ *

+ * Note that typically DocStoreEmbedded is used on @ManyToOne and @OneToMany + * properties to indicate what part of the nested document should be included. + *

+ * + *

Example:

+ *
{@code
+   *
+   * // only include the customer id and name
+   * @DocStore(doc = "id,name")
+   * @Entity @Table(name = "o_order")
+   * public class Customer {
+   *
+   * }
+ */ + String doc() default ""; +} diff --git a/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java b/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java new file mode 100644 index 000000000..66adc5e78 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java @@ -0,0 +1,37 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the property is included in the parent document store index. + * + *
{@code
+ *
+ *
+ * @DocStore
+ * @Entity @Table(name = "o_order")
+ * public class Order {
+ *
+ *   ...
+ *   // include some customer details including
+ *   // nested billingAddress
+ *   @DocStoreEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
+ *   @ManyToOne
+ *   Customer customer;
+ *
+ *
+ * }
+ */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocStoreEmbedded { + + /** + * The properties on the embedded bean to include in the index. + */ + String doc() default ""; + +} diff --git a/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java b/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java new file mode 100644 index 000000000..dcf8a72c0 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java @@ -0,0 +1,38 @@ +package com.avaje.ebean.annotation; + +/** + * Defines the behavior options when a Insert, Update or Delete event occurs + * on a bean with an associated ElasticSearch index. + *

+ * For some indexes or some transactions it can be beneficial to queue the event + * for later processing rather than look to update ElasticSearch at that time. + *

+ */ +public enum DocStoreEvent { + + /** + * Add the event to the queue for processing later (delaying the update to the document store). + */ + QUEUE, + + /** + * Update the document store when transaction succeeds. + */ + UPDATE, + + /** + * Ignore the event and not update the document store. + *

+ * This can be used on a index or for a transaction where you want to have more + * manual programmatic control over the updating of the document store. Say you want to + * IGNORE on a particular transaction and instead manually queue a bulk update. + *

+ */ + IGNORE, + + /** + * The actual mode of QUEUE, UPDATE or IGNORE is set from the default configuration. + */ + DEFAULT + +} diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java index bf1abcfdf..24462e614 100644 --- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java @@ -219,6 +219,18 @@ public final class EntityBeanIntercept implements Serializable { this.fullyLoadedBean = fullyLoadedBean; } + /** + * Check each property to see if the bean is partially loaded. + */ + public boolean isPartial() { + for (int i = 0; i < loadedProps.length; i++) { + if (!loadedProps[i]) { + return true; + } + } + return false; + } + /** * Return true if this bean has been directly modified (it has oldValues) or * if any embedded beans are either new or dirty (and hence need saving). @@ -469,7 +481,7 @@ public final class EntityBeanIntercept implements Serializable { public void setLoadedProperty(int propertyIndex) { loadedProps[propertyIndex] = true; } - + /** * Return true if the property is loaded. */ @@ -560,6 +572,23 @@ public final class EntityBeanIntercept implements Serializable { return props; } + /** + * Return the array of flags indicating the dirty properties. + */ + public boolean[] getDirtyProperties() { + int len = getPropertyLength(); + boolean[] dirties = new boolean[len]; + for (int i = 0; i < len; i++) { + if (changedProps != null && changedProps[i]) { + dirties[i] = true; + } else if (embeddedDirty != null && embeddedDirty[i]) { + // an embedded property has been changed - recurse + dirties[i] = true; + } + } + return dirties; + } + /** * Return the set of dirty properties. */ diff --git a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java index 0c9357ffa..e1d1c87f8 100644 --- a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java +++ b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java @@ -10,12 +10,12 @@ package com.avaje.ebean.bean; public interface PersistenceContext { /** - * Put the entity bean into the PersistanceContext. + * Put the entity bean into the PersistenceContext. */ void put(Object id, Object bean); /** - * Put the entity bean into the PersistanceContext if one is not already + * Put the entity bean into the PersistenceContext if one is not already * present (for this id). *

* Returns an existing entity bean (if one is already there) and otherwise diff --git a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java index ca0a75c74..658d2d65f 100644 --- a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java +++ b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java @@ -90,6 +90,13 @@ public class ClassLoadConfig { return context.forName(name); } + /** + * Return the classLoader to use for service loading etc. + */ + public ClassLoader getClassLoader() { + return context.getClassLoader(); + } + /** * Wraps the preferred, caller and context class loaders. */ @@ -138,6 +145,9 @@ public class ClassLoadConfig { return Class.forName(name, true, classLoader); } + ClassLoader getClassLoader() { + return preferredLoader != null ? preferredLoader : contextLoader; + } } } diff --git a/src/main/java/com/avaje/ebean/config/DocStoreConfig.java b/src/main/java/com/avaje/ebean/config/DocStoreConfig.java new file mode 100644 index 000000000..f8169c644 --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/DocStoreConfig.java @@ -0,0 +1,138 @@ +package com.avaje.ebean.config; + +import com.avaje.ebean.annotation.DocStoreEvent; + +/** + * Configuration for the Document store (ElasticSearch) integration. + */ +public class DocStoreConfig { + + /** + * True when the Document store integration is active/on. + */ + boolean active; + + /** + * When true the Document store should drop and re-create any document mapping (like DDL). + */ + boolean dropCreate; + + /** + * The URL of the Document store server. For example: http://localhost:9200. + */ + String url; + + /** + * The default mode used by indexes. + */ + DocStoreEvent persist = DocStoreEvent.UPDATE; + + /** + * The default batch size to use for the Bulk API calls. + */ + int bulkBatchSize = 1000; + + + /** + * Return true if the Document store (ElasticSearch) integration is active. + */ + public boolean isActive() { + return active; + } + + /** + * Set to true to make the Document store (ElasticSearch) integration active. + */ + public void setActive(boolean active) { + this.active = active; + } + + /** + * Return true if the document store should recreate mappings. + */ + public boolean isDropCreate() { + return dropCreate; + } + + /** + * Set to true if the document store should recreate mappings. + */ + public void setDropCreate(boolean dropCreate) { + this.dropCreate = dropCreate; + } + + /** + * Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated + * Document store. + */ + public DocStoreEvent getPersist() { + return persist; + } + + /** + * Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated + * Document store. + *

    + *
  • DocStoreEvent.UPDATE - build and send message to Bulk API
  • + *
  • DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing
  • + *
  • DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index
  • + *
+ *

+ * You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing + * is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple + * updates for an index entry into a single update. + *

+ *

+ * You might choose to use IGNORE when you have your own external process for updating the indexes. In this case + * you don't want Ebean to do anything when the data changes. + *

+ */ + public void setPersist(DocStoreEvent persist) { + this.persist = persist; + } + + /** + * Return the URL to the Document store. + */ + public String getUrl() { + return url; + } + + /** + * Set the URL to the Document store server. + * + * For a local ElasticSearch server this would be: http://localhost:9200 + */ + public void setUrl(String url) { + this.url = url; + } + + + /** + * Return the default batch size to use for calls to the Bulk API. + */ + public int getBulkBatchSize() { + return bulkBatchSize; + } + + /** + * Set the default batch size to use for calls to the Bulk API. + *

+ * The batch size can be set on a transaction via {@link com.avaje.ebean.Transaction#setDocStoreUpdateBatchSize(int)}. + *

+ */ + public void setBulkBatchSize(int bulkBatchSize) { + this.bulkBatchSize = bulkBatchSize; + } + + /** + * Load settings specified in properties files. + */ + public void loadSettings(PropertiesWrapper properties) { + + active = properties.getBoolean("docstore.active", active); + url = properties.get("docstore.url", url); + persist = properties.getEnum(DocStoreEvent.class, "docstore.persist", persist); + bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize); + } +} diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index d13fe2c97..b50a8d827 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -25,8 +25,10 @@ import com.fasterxml.jackson.core.JsonFactory; import javax.sql.DataSource; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Properties; +import java.util.ServiceLoader; /** * The configuration used for creating a EbeanServer. @@ -129,6 +131,11 @@ public class ServerConfig { */ private String classPathReaderClassName; + /** + * Configuration for the ElasticSearch integration. + */ + private DocStoreConfig docStoreConfig = new DocStoreConfig(); + /** * This is used to populate @WhoCreated, @WhoModified and * support other audit features (who executed a query etc). @@ -283,7 +290,7 @@ public class ServerConfig { private DbConstraintNaming constraintNaming = new DbConstraintNaming(); /** - * Behaviour of update to include on the change properties. + * Behaviour of update to include on the change properties. */ private boolean updateChangesOnly = true; @@ -1153,6 +1160,20 @@ public class ServerConfig { this.namingConvention = namingConvention; } + /** + * Return the configuration for the ElasticSearch integration. + */ + public DocStoreConfig getDocStoreConfig() { + return docStoreConfig; + } + + /** + * Set the configuration for the ElasticSearch integration. + */ + public void setDocStoreConfig(DocStoreConfig docStoreConfig) { + this.docStoreConfig = docStoreConfig; + } + /** * Return the constraint naming convention used in DDL generation. */ @@ -2134,6 +2155,23 @@ public class ServerConfig { this.classLoadConfig = classLoadConfig; } + /** + * Return the service loader using the classLoader defined in ClassLoadConfig. + */ + public ServiceLoader serviceLoad(Class spiService) { + + return ServiceLoader.load(spiService, classLoadConfig.getClassLoader()); + } + + /** + * Return the first service using the service loader (or null). + */ + public T service(Class spiService) { + ServiceLoader load = serviceLoad(spiService); + Iterator serviceInstances = load.iterator(); + return serviceInstances.hasNext() ? serviceInstances.next() : null; + } + /** * Load settings from ebean.properties. */ @@ -2211,6 +2249,13 @@ public class ServerConfig { dataSourceConfig.loadSettings(p.withPrefix("datasource")); } + /** + * This is broken out for the same reason as above - preserve existing behaviour but let it be overridden. + */ + protected void loadDocStoreSettings(PropertiesWrapper p) { + docStoreConfig.loadSettings(p); + } + /** * This is broken out for the same reason as above - preserve existing behaviour but let it be overridden. */ @@ -2239,6 +2284,11 @@ public class ServerConfig { } loadDataSourceSettings(p); + if (docStoreConfig == null) { + docStoreConfig = new DocStoreConfig(); + } + docStoreConfig.loadSettings(p); + explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode); autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode); useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager); diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java index 3b24b2337..3db639e35 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java +++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java @@ -38,7 +38,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor { table.setComment(descriptor.getDbComment()); if (descriptor.isHistorySupport()) { table.setWithHistory(true); - BeanProperty whenCreated = descriptor.findWhenCreatedProperty(); + BeanProperty whenCreated = descriptor.getWhenCreatedProperty(); if (whenCreated != null) { table.setWhenCreatedColumn(whenCreated.getDbColumn()); } diff --git a/src/main/java/com/avaje/ebean/plugin/BeanDocType.java b/src/main/java/com/avaje/ebean/plugin/BeanDocType.java new file mode 100644 index 000000000..e679968f6 --- /dev/null +++ b/src/main/java/com/avaje/ebean/plugin/BeanDocType.java @@ -0,0 +1,72 @@ +package com.avaje.ebean.plugin; + +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.Query; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; + +import java.io.IOException; + +/** + * Doc store functions for a specific entity bean type. + * + * @param The type of entity bean + */ +public interface BeanDocType { + + /** + * Return the doc store index type for this bean type. + */ + String getIndexType(); + + /** + * Return the doc store index name for this bean type. + */ + String getIndexName(); + + /** + * Apply the appropriate fetch path to the query such that the query returns beans matching + * the document store structure with the expected embedded properties. + */ + void applyPath(Query spiQuery); + + /** + * Return the FetchPath for the embedded document. + */ + FetchPath getEmbedded(String path); + + /** + * For embedded 'many' properties we need a FetchPath relative to the root which is used to + * build and replace the embedded list. + */ + FetchPath getEmbeddedManyRoot(String path); + + /** + * Return a 'raw' property mapped for the given property. + * If none exists the given property is returned. + */ + String rawProperty(String property); + + /** + * Store the bean in the doc store index. + *

+ * This somewhat assumes the bean is fetched with appropriate path properties + * to match the expected document structure. + */ + void index(Object idValue, T bean, DocStoreUpdateContext txn) throws IOException; + + /** + * Add a delete by Id to the doc store. + */ + void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException; + + /** + * Add a embedded document update to the doc store. + * + * @param idValue the Id value of the bean holding the embedded document + * @param embeddedProperty the embedded property + * @param embeddedRawContent the content of the embedded document in JSON form + * @param txn the doc store transaction to add the update to + */ + void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException; + +} diff --git a/src/main/java/com/avaje/ebean/plugin/BeanType.java b/src/main/java/com/avaje/ebean/plugin/BeanType.java index ce44337e5..79d6c784d 100644 --- a/src/main/java/com/avaje/ebean/plugin/BeanType.java +++ b/src/main/java/com/avaje/ebean/plugin/BeanType.java @@ -5,32 +5,96 @@ import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping; +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; +import java.util.Collection; /** * Information and methods on BeanDescriptors made available to plugins. */ public interface BeanType { + /** + * Return the full name of the bean type. + */ + String getFullName(); + /** * Return the class type this BeanDescriptor describes. */ Class getBeanType(); + /** + * Return the type bean for an OneToMany or ManyToOne or ManyToMany property. + */ + BeanType getBeanTypeAtPath(String propertyName); + + /** + * Return all the properties for this bean type. + */ + Collection allProperties(); + + /** + * Return the Id property. + */ + Property getIdProperty(); + + /** + * Return the when modified property if there is one defined. + */ + Property getWhenModifiedProperty(); + + /** + * Return the when created property if there is one defined. + */ + Property getWhenCreatedProperty(); + + /** + * Return the SpiProperty for a property to read values from a bean. + */ + Property getProperty(String propertyName); + + /** + * Return the SpiExpressionPath for a given property path. + *

+ * This can return a property or nested property path. + *

+ */ + ExpressionPath getExpressionPath(String path); + /** * Return true if the property is a valid known property or path for the given bean type. */ boolean isValidExpression(String property); - /** - * Return the base table this bean type maps to. - */ + /** + * Return the base table this bean type maps to. + */ String getBaseTable(); + /** + * Create a new instance of the bean. + */ + T createBean(); + + /** + * Return the bean id. This is the same as getBeanId() but without the generic type. + */ + Object beanId(Object bean); + /** * Return the id value for the given bean. */ Object getBeanId(T bean); + /** + * Set the id value to the bean. + */ + void setBeanId(T bean, Object idValue); + /** * Return the bean persist controller. */ @@ -61,4 +125,33 @@ public interface BeanType { */ String getSequenceName(); + /** + * Return true if this bean type has doc store backing. + */ + boolean isDocStoreMapped(); + + /** + * Return the DocumentMapping for this bean type. + *

+ * This is the document structure and mapping options for how this bean type is mapped + * for the document store. + *

+ */ + DocumentMapping getDocMapping(); + + /** + * Return the doc store queueId for this bean type. + */ + String getDocStoreQueueId(); + + /** + * Return the doc store support for this bean type.\ + */ + BeanDocType docStore(); + + /** + * Read the JSON content returning the bean. + */ + T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException; + } diff --git a/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java b/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java new file mode 100644 index 000000000..e1ef6fbcb --- /dev/null +++ b/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java @@ -0,0 +1,20 @@ +package com.avaje.ebean.plugin; + +/** + * A dot notation expression path. + */ +public interface ExpressionPath { + + /** + * Return true if there is a property on the path that is a many property. + */ + boolean containsMany(); + + /** + * Set a value to the bean for this expression path. + * + * @param bean the bean to set the value on + * @param value the value to set + */ + void set(Object bean, Object value); +} diff --git a/src/main/java/com/avaje/ebean/plugin/Property.java b/src/main/java/com/avaje/ebean/plugin/Property.java new file mode 100644 index 000000000..e8b45a3fb --- /dev/null +++ b/src/main/java/com/avaje/ebean/plugin/Property.java @@ -0,0 +1,22 @@ +package com.avaje.ebean.plugin; + +/** + * Property of a entity bean that can be read. + */ +public interface Property { + + /** + * Return the name of the property. + */ + String getName(); + + /** + * Return the value of the property on the given bean. + */ + Object getVal(Object bean); + + /** + * Return true if this is a OneToMany or ManyToMany property. + */ + boolean isMany(); +} diff --git a/src/main/java/com/avaje/ebean/plugin/SpiServer.java b/src/main/java/com/avaje/ebean/plugin/SpiServer.java index 854e96817..12a196339 100644 --- a/src/main/java/com/avaje/ebean/plugin/SpiServer.java +++ b/src/main/java/com/avaje/ebean/plugin/SpiServer.java @@ -36,4 +36,8 @@ public interface SpiServer extends EbeanServer { */ List> getBeanTypes(String baseTableName); + /** + * Return the bean type for a given doc store queueId. + */ + BeanType getBeanTypeForQueueId(String queueId); } diff --git a/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java new file mode 100644 index 000000000..6c78f089b --- /dev/null +++ b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java @@ -0,0 +1,35 @@ +package com.avaje.ebean.text.json; + +import com.avaje.ebean.bean.PersistenceContext; +import com.fasterxml.jackson.core.JsonParser; + +/** + * Provides a JSON reader that can hold a persistence context and load context while reading JSON. + *

+ * This provides a mechanism such that an object loaded from JSON can have unique instances + * by using a persistence context and also support further lazy loading (via a load context). + *

+ */ +public interface JsonBeanReader { + + /** + * Read the JSON returning a bean. + */ + T read(); + + /** + * Create a new reader taking the context from the existing one but using a new JsonParser. + */ + JsonBeanReader forJson(JsonParser moreJson); + + /** + * Add a bean explicitly to the persistence context. + */ + void persistenceContextPut(Object beanId, T currentBean); + + /** + * Return the persistence context if one is being used. + */ + PersistenceContext getPersistenceContext(); + +} diff --git a/src/main/java/com/avaje/ebean/text/json/JsonContext.java b/src/main/java/com/avaje/ebean/text/json/JsonContext.java index 28b540016..adcd55ba5 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonContext.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonContext.java @@ -56,6 +56,16 @@ public interface JsonContext { */ T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException; + /** + * Create and return a new bean reading for the bean type given the JSON options and source. + *

+ * Note that JsonOption provides an option for setting a persistence context and also enabling + * further lazy loading. Further lazy loading requires a persistence context so if that is set + * on then a persistence context is created if there is not one set. + *

+ */ + JsonBeanReader createBeanReader(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException; + /** * Convert json string input into a list of beans of a specific type. * diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java index 4e4f04cbb..aee1d9407 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java @@ -1,5 +1,7 @@ package com.avaje.ebean.text.json; +import com.avaje.ebean.bean.PersistenceContext; + import java.util.LinkedHashMap; import java.util.Map; @@ -17,6 +19,10 @@ public class JsonReadOptions { protected Object objectMapper; + protected boolean enableLazyLoading; + + protected PersistenceContext persistenceContext; + /** * Default constructor. */ @@ -46,6 +52,25 @@ public class JsonReadOptions { return this; } + /** + * Return true if lazy loading is enabled after the objects are loaded. + */ + public boolean isEnableLazyLoading() { + return enableLazyLoading; + } + + /** + * Set to true to enable lazy loading on partially populated beans. + *

+ * If this is set to true a persistence context will be created if one has + * not already been supplied. + *

+ */ + public JsonReadOptions setEnableLazyLoading(boolean enableLazyLoading) { + this.enableLazyLoading = enableLazyLoading; + return this; + } + /** * Return the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig). */ @@ -56,7 +81,23 @@ public class JsonReadOptions { /** * Set the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig). */ - public void setObjectMapper(Object objectMapper) { + public JsonReadOptions setObjectMapper(Object objectMapper) { this.objectMapper = objectMapper; + return this; + } + + /** + * Set the persistence context to use when building the object graph from the JSON. + */ + public JsonReadOptions setPersistenceContext(PersistenceContext persistenceContext) { + this.persistenceContext = persistenceContext; + return this; + } + + /** + * Return the persistence context to use when marshalling JSON. + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/text/json/JsonScalar.java b/src/main/java/com/avaje/ebean/text/json/JsonScalar.java index f1cd9cc2f..07d006980 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonScalar.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonScalar.java @@ -3,9 +3,13 @@ package com.avaje.ebean.text.json; import java.io.IOException; /** - * Writes any scalar type known to Ebean the Jackson generator. + * Writes any scalar type known to Ebean to the underlying Jackson generator. */ public interface JsonScalar { + /** + * Write the scalar type to JSON where the value can be any type known to Ebean + * including Enums, Java8 time types, Joda types, URL, URI etc. + */ void write(String name, Object value) throws IOException; } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java index cd8701768..d067559e9 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java @@ -29,4 +29,5 @@ public interface LoadManyBuffer { void configureQuery(SpiQuery query); + boolean isUseDocStore(); } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java index e6d918adb..9c845714d 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java @@ -144,7 +144,7 @@ public class LoadManyRequest extends LoadRequest { query.setLazyLoadForParents(many); List idList = getParentIdList(batchSize); - many.addWhereParentIdIn(query, idList); + many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore()); query.setPersistenceContext(loadContext.getPersistenceContext()); diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java index f2a15ebd2..cd97cbaf4 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.api; import com.avaje.ebean.TransactionCallback; +import com.avaje.ebean.annotation.DocStoreEvent; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.config.PersistBatch; import com.avaje.ebean.event.changelog.BeanChange; @@ -59,6 +60,26 @@ public class ScopedTransaction implements SpiTransaction { } } + @Override + public DocStoreEvent getDocStoreUpdateMode() { + return transaction.getDocStoreUpdateMode(); + } + + @Override + public void setDocStoreUpdateMode(DocStoreEvent indexUpdateMode) { + transaction.setDocStoreUpdateMode(indexUpdateMode); + } + + @Override + public int getDocStoreBulkBatchSize() { + return transaction.getDocStoreBulkBatchSize(); + } + + @Override + public void setDocStoreUpdateBatchSize(int batchSize) { + transaction.setDocStoreUpdateBatchSize(batchSize); + } + @Override public String getLogPrefix() { return transaction.getLogPrefix(); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java index 24830422f..a01e2e5bd 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java @@ -83,6 +83,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ BeanDescriptor getBeanDescriptorById(String className); + /** + * Return BeanDescriptor using it's unique doc store queueId. + */ + BeanDescriptor getBeanDescriptorByQueueId(String queueId); + /** * Return BeanDescriptors mapped to this table. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java index bacd508de..59aec8e2b 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java @@ -3,6 +3,9 @@ package com.avaje.ebeaninternal.api; import com.avaje.ebean.Expression; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext; + +import java.io.IOException; /** @@ -10,6 +13,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; */ public interface SpiExpression extends Expression { + /** + * Write the expression as an elastic search expression. + */ + void writeElastic(ElasticExpressionContext context) throws IOException; + /** * Process "Many" properties populating ManyWhereJoins. *

diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 11067a139..df2c81301 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -16,10 +16,12 @@ import com.avaje.ebeaninternal.server.autotune.ProfilingListener; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext; import com.avaje.ebeaninternal.server.query.CancelableQuery; import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import java.io.IOException; import java.sql.Timestamp; import java.util.List; import java.util.Set; @@ -128,6 +130,26 @@ public interface SpiQuery extends Query { } } + /** + * Write the query as an elastic search query. + */ + void writeElastic(ElasticExpressionContext context) throws IOException; + + /** + * Return true if AutoTune should be attempted on this query. + */ + boolean isAutoTunable(); + + /** + * Return the bean descriptor for this query. + */ + BeanDescriptor getBeanDescriptor(); + + /** + * Return true if this query should be executed against the doc store. + */ + boolean isUseDocStore(); + /** * Return the PersistenceContextScope that this query should use. *

@@ -276,18 +298,13 @@ public interface SpiQuery extends Query { /** * Return the lazy loading 'many' property. */ - BeanPropertyAssocMany getLazyLoadForParentsProperty(); + BeanPropertyAssocMany getLazyLoadMany(); /** * Set the load mode (+lazy or +query) and the load description. */ void setLoadDescription(String loadMode, String loadDescription); - /** - * Set the BeanDescriptor for the root type of this query. - */ - void setBeanDescriptor(BeanDescriptor desc); - /** * Return the joins required to support predicates on the many properties. */ @@ -639,9 +656,9 @@ public interface SpiQuery extends Query { */ boolean isDisableReadAudit(); - /** - * Return true if this is a query executing in the background. - */ + /** + * Return true if this is a query executing in the background. + */ boolean isFutureFetch(); /** diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java index 291bc6954..5be5af0a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java @@ -4,6 +4,7 @@ import java.sql.Connection; import java.util.List; import com.avaje.ebean.Transaction; +import com.avaje.ebean.annotation.DocStoreEvent; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.changelog.BeanChange; import com.avaje.ebean.event.changelog.ChangeSet; @@ -20,7 +21,7 @@ import com.avaje.ebeaninternal.server.persist.BatchControl; public interface SpiTransaction extends Transaction { /** - * Return the string prefix with the transactin id and label used in logging. + * Return the string prefix with the transaction id and label used in logging. */ String getLogPrefix(); @@ -102,6 +103,20 @@ public interface SpiTransaction extends Transaction { */ Boolean isUpdateAllLoadedProperties(); + /** + * Return the batchSize specifically set for this transaction or 0. + *

+ * Returning 0 implies to use the system wide default batch size. + *

+ */ + DocStoreEvent getDocStoreUpdateMode(); + + /** + * Return the batch size to us for ElasticSearch Bulk API calls + * as a result of this transaction. + */ + int getDocStoreBulkBatchSize(); + /** * Return the batchSize specifically set for this transaction or 0. *

@@ -157,12 +172,12 @@ public interface SpiTransaction extends Transaction { boolean isBatchThisRequest(PersistRequest.Type type); /** - * Return the queue used to batch up persist requests. + * Return the BatchControl used to batch up persist requests. */ BatchControl getBatchControl(); /** - * Set the queue used to batch up persist requests. There should only be one + * Set the BatchControl used to batch up persist requests. There should only be one * PersistQueue set per transaction. */ void setBatchControl(BatchControl control); diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java index 14a52af42..209c71a96 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.api; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap; @@ -120,4 +121,16 @@ public class TransactionEvent implements Serializable { } } + /** + * Add any relevant PersistRequestBean's to DocStoreUpdates for later processing. + */ + public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) { + + List> persistRequestBeans = getPersistRequestBeans(); + if (persistRequestBeans != null) { + for (int i=0; i< persistRequestBeans.size(); i++) { + persistRequestBeans.get(i).addDocStoreUpdates(docStoreUpdates); + } + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java index 23da4ce10..028f521d6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java @@ -36,16 +36,20 @@ public abstract class BeanRequest { * A transaction may have been passed in or active in the thread local. If * not then create one implicitly to handle the request. *

+ * + * @return True if a transaction was set (from current or created). */ - public void createImplicitTransIfRequired() { - if (transaction == null) { - transaction = ebeanServer.getCurrentServerTransaction(); - if (transaction == null || !transaction.isActive()) { - // create an implicit transaction to execute this query - transaction = ebeanServer.createServerTransaction(false, -1); - createdTransaction = true; - } - } + public boolean createImplicitTransIfRequired() { + if (transaction != null) { + return false; + } + transaction = ebeanServer.getCurrentServerTransaction(); + if (transaction == null || !transaction.isActive()) { + // create an implicit transaction to execute this query + transaction = ebeanServer.createServerTransaction(false, -1); + createdTransaction = true; + } + return true; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 7b4e9c44c..c4f35fd77 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -66,6 +66,7 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager; import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager; import com.avaje.ebeaninternal.util.ParamTypeHelper; import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; +import com.avaje.ebeanservice.docstore.api.DocStoreIntegration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,6 +153,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final JsonContext jsonContext; + private final DocumentStore documentStore; + private final MetaInfoManager metaInfoManager; /** @@ -219,8 +222,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.maxCallStack = serverConfig.getMaxCallStack(); this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked(); - this.transactionManager = config.getTransactionManager(); - this.transactionScopeManager = config.getTransactionScopeManager(); this.persister = config.createPersister(this); this.queryEngine = config.createOrmQueryEngine(); @@ -232,6 +233,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.beanLoader = new DefaultBeanLoader(this); this.jsonContext = config.createJsonContext(this); + + DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this); + this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor()); + this.transactionScopeManager = config.createTransactionScopeManager(transactionManager); + this.documentStore = docStoreComponents.documentStore(); + this.serverPlugins = config.getPlugins(); this.ddlGenerator = new DdlGenerator(this, serverConfig); @@ -902,7 +909,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { public Query createNamedQuery(Class beanType, String namedQuery) throws PersistenceException { - BeanDescriptor desc = getBeanDescriptor(beanType); + BeanDescriptor desc = getBeanDescriptor(beanType); if (desc == null) { throw new PersistenceException("Is " + beanType.getName() + " an Entity Bean? BeanDescriptor not found?"); } @@ -912,7 +919,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } // this will parse the query - return new DefaultOrmQuery(beanType, this, expressionFactory, deployQuery); + return new DefaultOrmQuery(desc, this, expressionFactory, deployQuery); } @Override @@ -947,10 +954,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } public Query createQuery(Class beanType, String query) { - BeanDescriptor desc = getBeanDescriptor(beanType); + BeanDescriptor desc = getBeanDescriptor(beanType); if (desc == null) { - String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; - throw new PersistenceException(m); + throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?"); } switch (desc.getEntityType()) { case SQL: @@ -959,10 +965,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } // use the "default" SqlSelect DeployNamedQuery defaultSqlSelect = desc.getNamedQuery("default"); - return new DefaultOrmQuery(beanType, this, expressionFactory, defaultSqlSelect); + return new DefaultOrmQuery(desc, this, expressionFactory, defaultSqlSelect); default: - return new DefaultOrmQuery(beanType, this, expressionFactory, query); + return new DefaultOrmQuery(desc, this, expressionFactory, query); } } @@ -1042,15 +1048,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { SpiQuery spiQuery = (SpiQuery) query; spiQuery.setType(type); - BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType()); - spiQuery.setBeanDescriptor(desc); - - return createQueryRequest(desc, spiQuery, t); + return createQueryRequest(spiQuery, t); } - private SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) { + private SpiOrmQueryRequest createQueryRequest(SpiQuery query, Transaction t) { - if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) { + if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) { // use deployment FetchType.LAZY/EAGER annotations // to define the 'default' select clause query.setDefaultSelectClause(); @@ -1063,7 +1066,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { query.setOrigin(createCallStack()); } - OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t); + OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, (SpiTransaction) t); request.prepareQuery(); return request; @@ -1073,7 +1076,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Try to get the object out of the persistence context. */ @SuppressWarnings("unchecked") - private T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor beanDescriptor, SpiQuery query) { + private T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery query) { SpiTransaction t = (SpiTransaction) transaction; if (t == null) { @@ -1084,7 +1087,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { // first look in the transaction scoped persistence context context = t.getPersistenceContext(); if (context != null) { - WithOption o = context.getWithOption(beanDescriptor.getBeanType(), query.getId()); + WithOption o = context.getWithOption(query.getBeanType(), query.getId()); if (o != null) { if (o.isDeleted()) { // Bean was previously deleted in the same transaction / persistence context @@ -1096,13 +1099,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) { + BeanDescriptor desc = query.getBeanDescriptor(); + if (!desc.calculateUseCache(query.isUseBeanCache())) { // not using bean cache return null; } // Hit the L2 bean cache - return beanDescriptor.cacheBeanGet(query, context); + return desc.cacheBeanGet(query, context); } /** @@ -1126,19 +1130,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { SpiQuery spiQuery = (SpiQuery) query; spiQuery.setType(Type.BEAN); - BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType()); - spiQuery.setBeanDescriptor(desc); - if (SpiQuery.Mode.NORMAL.equals(spiQuery.getMode()) && !spiQuery.isLoadBeanCache()) { // See if we can skip doing the fetch completely by getting the bean from the // persistence context or the bean cache - T bean = findIdCheckPersistenceContextAndCache(t, desc, spiQuery); + T bean = findIdCheckPersistenceContextAndCache(t, spiQuery); if (bean != null) { return bean; } } - SpiOrmQueryRequest request = createQueryRequest(desc, spiQuery, t); + SpiOrmQueryRequest request = createQueryRequest(spiQuery, t); try { request.initTransIfRequired(); return (T) request.findId(); @@ -1340,6 +1341,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { throw new PersistenceException("maxRows must be specified for findPagedList() query"); } + if (spiQuery.isUseDocStore()) { + return docStore().findPagedList(query); + } + return new LimitOffsetPagedList(this, spiQuery); } @@ -1397,6 +1402,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (result != null) { return (List) result; } + if (request.isUseDocStore()) { + return docStore().findList(query); + } try { request.initTransIfRequired(); @@ -2011,6 +2019,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return beanDescriptorManager.getBeanTypes(tableName); } + @Override + public BeanType getBeanTypeForQueueId(String queueId) { + return getBeanDescriptorByQueueId(queueId); + } + + @Override + public BeanDescriptor getBeanDescriptorByQueueId(String queueId) { + return beanDescriptorManager.getBeanDescriptorByQueueId(queueId); + } + /** * Return the SPI bean types for the given bean class. */ @@ -2113,6 +2131,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return callStackFactory.createCallStack(finalTrace); } + @Override + public DocumentStore docStore() { + return documentStore; + } + @Override public JsonContext json() { // immutable thread safe so return shared instance diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index 8dab12bd1..e0e56ef3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -12,6 +12,7 @@ import com.avaje.ebean.event.changelog.ChangeLogRegister; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; import com.avaje.ebean.plugin.Plugin; +import com.avaje.ebean.plugin.SpiServer; import com.avaje.ebean.text.json.JsonContext; import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -46,6 +47,10 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager; import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager; import com.avaje.ebeaninternal.server.type.DefaultTypeManager; import com.avaje.ebeaninternal.server.type.TypeManager; +import com.avaje.ebeanservice.docstore.api.DocStoreFactory; +import com.avaje.ebeanservice.docstore.api.DocStoreIntegration; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; +import com.avaje.ebeanservice.docstore.none.NoneDocStoreFactory; import com.fasterxml.jackson.core.JsonFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,10 +87,6 @@ public class InternalConfiguration { private final BeanDescriptorManager beanDescriptorManager; - private final TransactionManager transactionManager; - - private final TransactionScopeManager transactionScopeManager; - private final CQueryEngine cQueryEngine; private final ClusterManager clusterManager; @@ -100,6 +101,8 @@ public class InternalConfiguration { private final JsonFactory jsonFactory; + private final DocStoreFactory docStoreFactory; + /** * List of plugins (that ultimately the DefaultServer configures late in construction). */ @@ -109,6 +112,7 @@ public class InternalConfiguration { ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor, ServerConfig serverConfig, BootupClasses bootupClasses) { + this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class)); this.jsonFactory = serverConfig.getJsonFactory(); this.xmlConfig = xmlConfig; this.clusterManager = clusterManager; @@ -130,25 +134,21 @@ public class InternalConfiguration { Map asOfTableMapping = beanDescriptorManager.deploy(); Map draftTableMap = beanDescriptorManager.getDraftTableMap(); - this.transactionManager = createTransactionManager(); - DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform(); this.binder = getBinder(typeManager, databasePlatform); this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap); + } - ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager(); - if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) { - externalTransactionManager = new JtaTransactionManager(); - } - if (externalTransactionManager != null) { - externalTransactionManager.setTransactionManager(transactionManager); - this.transactionScopeManager = new ExternalTransactionScopeManager(transactionManager, externalTransactionManager); - logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]"); - } else { - this.transactionScopeManager = new DefaultTransactionScopeManager(transactionManager); - } + private DocStoreFactory initDocStoreFactory(DocStoreFactory service) { + return service == null ? new NoneDocStoreFactory() : service; + } + /** + * Return the doc store factory. + */ + public DocStoreFactory getDocStoreFactory() { + return docStoreFactory; } /** @@ -244,34 +244,6 @@ public class InternalConfiguration { return new NotSupportedJsonExpression(); } - /** - * Create the TransactionManager taking into account autoCommit mode. - */ - private TransactionManager createTransactionManager() { - - if (serverConfig.isExplicitTransactionBeginMode()) { - return new ExplicitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses()); - } - - if (isAutoCommitMode()) { - return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses()); - } - - return new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses()); - } - - /** - * Return true if autoCommit mode is on. - */ - private boolean isAutoCommitMode() { - if (serverConfig.isAutoCommitMode()) { - // explicitly set - return true; - } - DataSource dataSource = serverConfig.getDataSource(); - return dataSource instanceof DataSourcePool && ((DataSourcePool) dataSource).getAutoCommit(); - } - public JsonContext createJsonContext(SpiEbeanServer server) { return new DJsonContext(server, jsonFactory, typeManager); @@ -317,10 +289,6 @@ public class InternalConfiguration { return expressionFactory; } - public TypeManager getTypeManager() { - return typeManager; - } - public Binder getBinder() { return binder; } @@ -345,14 +313,6 @@ public class InternalConfiguration { return deployUtil; } - public TransactionManager getTransactionManager() { - return transactionManager; - } - - public TransactionScopeManager getTransactionScopeManager() { - return transactionScopeManager; - } - public CQueryEngine getCQueryEngine() { return cQueryEngine; } @@ -368,4 +328,57 @@ public class InternalConfiguration { public GeneratedPropertyFactory getGeneratedPropertyFactory() { return new GeneratedPropertyFactory(serverConfig); } + + /** + * Create the DocStoreIntegration components for the given server. + */ + public DocStoreIntegration createDocStoreIntegration(SpiServer server) { + return plugin(docStoreFactory.create(server)); + } + + /** + * Create the TransactionManager taking into account autoCommit mode. + */ + public TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) { + + if (serverConfig.isExplicitTransactionBeginMode()) { + return new ExplicitTransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses()); + } + + if (isAutoCommitMode()) { + return new AutoCommitTransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses()); + } + + return new TransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses()); + } + + /** + * Return true if autoCommit mode is on. + */ + private boolean isAutoCommitMode() { + if (serverConfig.isAutoCommitMode()) { + // explicitly set + return true; + } + DataSource dataSource = serverConfig.getDataSource(); + return dataSource instanceof DataSourcePool && ((DataSourcePool) dataSource).getAutoCommit(); + } + + /** + * Create the TransactionScopeManager taking into account JTA or external transaction manager. + */ + public TransactionScopeManager createTransactionScopeManager(TransactionManager transactionManager) { + + ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager(); + if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) { + externalTransactionManager = new JtaTransactionManager(); + } + if (externalTransactionManager != null) { + externalTransactionManager.setTransactionManager(transactionManager); + logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]"); + return new ExternalTransactionScopeManager(transactionManager, externalTransactionManager); + } else { + return new DefaultTransactionScopeManager(transactionManager); + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index 76ea1aaae..2c890081a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -68,11 +68,9 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe /** * Create the InternalQueryRequest. */ - public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, BeanDescriptor desc, SpiTransaction t) { - + public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, SpiTransaction t) { super(server, t); - - this.beanDescriptor = desc; + this.beanDescriptor = query.getBeanDescriptor(); this.rawSql = query.getRawSql(); this.finder = beanDescriptor.getBeanFinder(); this.queryEngine = queryEngine; @@ -133,6 +131,11 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return loadContext; } + @Override + public boolean isUseDocStore() { + return query.isUseDocStore(); + } + /** * Run BeanQueryAdapter preQuery() if needed. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index b55dbecc2..f0e3daa07 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.core; import com.avaje.ebean.ValuePair; import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.annotation.DocStoreEvent; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.event.BeanPersistController; @@ -19,9 +20,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.persist.BatchControl; import com.avaje.ebeaninternal.server.persist.PersistExecute; import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdate; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -30,7 +35,7 @@ import java.util.Set; /** * PersistRequest for insert update or delete of a bean. */ -public final class PersistRequestBean extends PersistRequest implements BeanPersistRequest { +public final class PersistRequestBean extends PersistRequest implements BeanPersistRequest, DocStoreUpdate { private final BeanManager beanManager; @@ -64,6 +69,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP private final boolean publish; + private DocStoreEvent docStoreEvent; + private ConcurrencyMode concurrencyMode; /** @@ -102,6 +109,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ private Set updatedProperties; + /** + * Flags indicating the dirty properties on the bean. + */ + private boolean[] dirtyProperties; + /** * Flag set when request is added to JDBC batch. */ @@ -135,7 +147,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP this.parentBean = parentBean; this.controller = beanDescriptor.getPersistController(); this.type = type; - + this.docStoreEvent = calcDocStoreEvent(transaction, type); if (saveRecurse) { this.persistCascade = t.isPersistCascade(); } @@ -157,6 +169,17 @@ public final class PersistRequestBean extends PersistRequest implements BeanP this.dirty = intercept.isDirty(); } + /** + * Return the document store event that should be used for this request. + * + * Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we + * don't want to send to the doc store. + */ + private DocStoreEvent calcDocStoreEvent(SpiTransaction txn, Type type) { + DocStoreEvent docStoreEvent = (txn == null) ? null : txn.getDocStoreUpdateMode(); + return beanDescriptor.getDocStoreEvent(type, docStoreEvent); + } + /** * Return true if the draftDirty property should be set to true for this request. */ @@ -178,7 +201,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP * Init the transaction and also check for batch on cascade escalation. */ public void initTransIfRequiredWithBatchCascade() { - createImplicitTransIfRequired(); + + if (createImplicitTransIfRequired()) { + docStoreEvent = calcDocStoreEvent(transaction, type); + } if (transaction.checkBatchEscalationOnCascade(this)) { // we escalated to use batch mode so flush when done // but if createdTransaction then commit will flush it @@ -239,6 +265,13 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return intercept.getDirtyPropertyNames(); } + /** + * Return the dirty properties on this request. + */ + public boolean[] getDirtyProperties() { + return dirtyProperties; + } + /** * Return true if any of the given property names are dirty. */ @@ -247,6 +280,19 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return intercept.hasDirtyProperty(propertyNames); } + /** + * Return true if any of the given properties are dirty. + */ + public boolean hasDirtyProperty(int[] propertyPositions) { + + for (int i = 0; i < propertyPositions.length; i++) { + if (dirtyProperties[propertyPositions[i]]) { + return true; + } + } + return false; + } + @Override public Map getUpdatedValues() { return intercept.getDirtyValues(); @@ -254,7 +300,16 @@ public final class PersistRequestBean extends PersistRequest implements BeanP public boolean isNotify() { this.notifyCache = beanDescriptor.isCacheNotify(publish); - return notifyCache || isNotifyPersistListener(); + return notifyCache || isNotifyPersistListener() || isDocStoreNotify(); + } + + /** + * Return true if this request should updateAdd an ElasticSearch index + * by queuing an event or direct updateAdd (via Bulk API). + */ + private boolean isDocStoreNotify() { + // Either queue or directly update the document store + return docStoreEvent != DocStoreEvent.IGNORE; } public boolean isNotifyPersistListener() { @@ -283,6 +338,45 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } } + /** + * Process the persist request updating the document store. + */ + public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException { + + switch (type) { + case INSERT: + beanDescriptor.docStoreInsert(idValue, this, txn); + break; + case UPDATE: + beanDescriptor.docStoreUpdate(idValue, this, txn); + break; + case DELETE: + beanDescriptor.docStoreDeleteById(idValue, txn); + break; + default: + throw new IllegalStateException("Invalid type " + type); + } + } + + /** + * Add this event to the queue entries in IndexUpdates. + */ + public void addToQueue(DocStoreUpdates docStoreUpdates) { + switch (type) { + case INSERT: + docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue); + break; + case UPDATE: + docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue); + break; + case DELETE: + docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue); + break; + default: + throw new IllegalStateException("Invalid type " + type); + } + } + public void addToPersistMap(BeanPersistIdMap beanPersistMap) { beanPersistMap.add(beanDescriptor, type, idValue); @@ -637,6 +731,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP controllerPost(); } + if (type == Type.UPDATE && docStoreEvent == DocStoreEvent.UPDATE) { + // get the dirty properties for update notification to the doc store + dirtyProperties = intercept.getDirtyProperties(); + } // if bean persisted again then should result in an update intercept.setLoaded(); if (isInsert()) { @@ -806,6 +904,29 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return updatedManysOnly; } + /** + * For requests that update document store add this event to either the list + * of queue events or list of update events. + */ + public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) { + + if (type == Type.UPDATE) { + beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates); + } + switch (docStoreEvent) { + case UPDATE: { + docStoreUpdates.addPersist(this); + return; + } + case QUEUE: { + if (type == Type.DELETE) { + docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue); + } else { + docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue); + } + } + } + } /** * Determine if all loaded properties should be used for an update. @@ -874,4 +995,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return Type.SOFT_DELETE == type; } + /** + * Set the value of the Version property on the bean. + */ + public void setVersionValue(Object versionValue) { + beanDescriptor.getVersionProperty().setValueIntercept(entityBean, versionValue); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java index b2b6d5a55..555dea8a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -116,4 +116,9 @@ public interface SpiOrmQueryRequest { * Mark the underlying transaction as not being query only. */ void markNotQueryOnly(); + + /** + * Return true if this query is expected to use the doc store. + */ + boolean isUseDocStore(); } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 27bcc3266..7557f1d80 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -7,6 +7,7 @@ import com.avaje.ebean.SqlUpdate; import com.avaje.ebean.Transaction; import com.avaje.ebean.ValuePair; import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.annotation.DocStoreEvent; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; @@ -28,8 +29,13 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare; import com.avaje.ebean.event.readaudit.ReadEvent; import com.avaje.ebean.meta.MetaBeanInfo; import com.avaje.ebean.meta.MetaQueryPlanStatistic; +import com.avaje.ebean.plugin.BeanDocType; import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebean.plugin.ExpressionPath; +import com.avaje.ebean.plugin.Property; +import com.avaje.ebean.text.json.JsonReadOptions; import com.avaje.ebeaninternal.api.CQueryPlanKey; +import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiTransaction; @@ -60,8 +66,13 @@ import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.util.SortByClause; -import com.avaje.ebeaninternal.util.SortByClause.Property; import com.avaje.ebeaninternal.util.SortByClauseParser; +import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; +import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder; +import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping; +import com.fasterxml.jackson.core.JsonParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -240,7 +251,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final BeanProperty versionProperty; private final int versionPropertyIndex; - + + private final BeanProperty whenModifiedProperty; + + private final BeanProperty whenCreatedProperty; + /** * Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries. */ @@ -342,10 +357,15 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final boolean cacheSharableBeans; + private final String docStoreQueueId; + + private DocumentMapping docMapping; + private final BeanDescriptorDraftHelp draftHelp; private final BeanDescriptorCacheHelp cacheHelp; private final BeanDescriptorJsonHelp jsonHelp; - + private final DocStoreBeanAdapter docStoreAdapter; + private final String defaultSelectClause; private final LinkedHashSet defaultSelectClauseSet; @@ -440,12 +460,15 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.derivedTableJoins = listHelper.getTableJoin(); boolean noRelationships = propertiesOne.length + propertiesMany.length == 0; - + this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly(); this.cacheHelp = new BeanDescriptorCacheHelp(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported); this.jsonHelp = new BeanDescriptorJsonHelp(this); this.draftHelp = new BeanDescriptorDraftHelp(this); - + + this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy); + this.docStoreQueueId = docStoreAdapter.getQueueId(); + // Check if there are no cascade save associated beans ( subject to change // in initialiseOther()). Note that if we are in an inheritance hierarchy // then we also need to check every BeanDescriptors in the InheritInfo as @@ -459,6 +482,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { // object used to handle Id values this.idBinder = owner.createIdBinder(idProperty); + this.whenModifiedProperty = findWhenModifiedProperty(); + this.whenCreatedProperty = findWhenCreatedProperty(); // derive the index position of the Id and Version properties if (Modifier.isAbstract(beanType.getModifiers())) { @@ -659,6 +684,17 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { namedUpdate.initialise(parser); } } + docStoreAdapter.registerPaths(); + } + + /** + * Initialise the document mapping. + */ + public void initialiseDocMapping() { + for (int i = 0; i < propertiesMany.length; i++) { + propertiesMany[i].initialisePostTarget(); + } + docMapping = docStoreAdapter.createDocMapping(); } public void initInheritInfo() { @@ -868,6 +904,84 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return inheritInfo == null || inheritInfo.isRoot(); } + /** + * Return true if this type maps to a root type of a doc store document (not embedded or ignored). + */ + @Override + public boolean isDocStoreMapped() { + return docStoreAdapter.isMapped(); + } + + /** + * Return the queueId used to uniquely identify this type when queuing an index updateAdd. + */ + @Override + public String getDocStoreQueueId() { + return docStoreQueueId; + } + + @Override + public DocumentMapping getDocMapping() { + return docMapping; + } + + /** + * Return the doc store helper for this bean type. + */ + @Override + public BeanDocType docStore() { + return docStoreAdapter; + } + + /** + * Return doc store adapter for internal use for processing persist requests. + */ + public DocStoreBeanAdapter docStoreAdapter() { + return docStoreAdapter; + } + + /** + * Build the Document mapping recursively with the given prefix relative to the root of the document. + */ + public void docStoreMapping(DocMappingBuilder mapping, String prefix) { + + if (prefix != null && idProperty != null) { + // id property not included in the + idProperty.docStoreMapping(mapping, prefix); + } + + for (BeanProperty prop: propertiesNonTransient) { + prop.docStoreMapping(mapping, prefix); + } + } + + /** + * Return the type of DocStoreEvent that should occur for this type of persist request + * given the transactions requested mode. + */ + public DocStoreEvent getDocStoreEvent(PersistRequest.Type persistType, DocStoreEvent txnMode) { + return docStoreAdapter.getEvent(persistType, txnMode); + } + + public void docStoreInsert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException { + docStoreAdapter.insert(idValue, persistRequest, bulkUpdate); + } + + public void docStoreUpdate(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException { + docStoreAdapter.update(idValue, persistRequest, bulkUpdate); + } + + /** + * Check if this update invalidates an embedded part of a doc store document. + */ + public void docStoreUpdateEmbedded(PersistRequestBean request, DocStoreUpdates docStoreUpdates) { + docStoreAdapter.updateEmbedded(request, docStoreUpdates); + } + + public void docStoreDeleteById(Object idValue, DocStoreUpdateContext txn) throws IOException { + docStoreAdapter.deleteById(idValue, txn); + } + public T publish(T draftBean, T liveBean) { return draftHelp.publish(draftBean, liveBean); } @@ -1219,10 +1333,24 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return deleteRecurseSkippable && !isBeanCaching(); } + /** + * Return the 'when modified' property if there is one defined. + */ + public BeanProperty getWhenModifiedProperty() { + return whenModifiedProperty; + } + + /** + * Return the 'when created' property if there is one defined. + */ + public BeanProperty getWhenCreatedProperty() { + return whenCreatedProperty; + } + /** * Find a property annotated with @WhenCreated or @CreatedTimestamp. */ - public BeanProperty findWhenCreatedProperty() { + private BeanProperty findWhenCreatedProperty() { for (int i = 0; i < propertiesBaseScalar.length; i++) { if (propertiesBaseScalar[i].isGeneratedWhenCreated()) { @@ -1235,7 +1363,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Find a property annotated with @WhenModified or @UpdatedTimestamp. */ - public BeanProperty findWhenModifiedProperty() { + private BeanProperty findWhenModifiedProperty() { for (int i = 0; i < propertiesBaseScalar.length; i++) { if (propertiesBaseScalar[i].isGeneratedWhenModified()) { @@ -1328,6 +1456,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return namedUpdates.get(name); } + @Override + public T createBean() { + return (T)createEntityBean(); + } + /** * Creates a new EntityBean. */ @@ -1401,6 +1534,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return targetDesc.getBeanPropertyFromPath(split[1]); } + @Override + public BeanType getBeanTypeAtPath(String path) { + return getBeanDescriptor(path); + } + /** * Return the BeanDescriptor for a given path of Associated One or Many beans. */ @@ -1473,6 +1611,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Return the class type this BeanDescriptor describes. */ + @Override public Class getBeanType() { return beanType; } @@ -1484,6 +1623,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { * instead. *

*/ + @Override public String getFullName() { return fullName; } @@ -1512,6 +1652,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return (idProperty == null) ? null : idProperty.getValue(bean); } + @Override + public Object beanId(Object bean) { + return getId((EntityBean) bean); + } + @Override public Object getBeanId(T bean) { return getId((EntityBean) bean); @@ -1552,6 +1697,14 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return idBinder.convertId(idValue); } + /** + * Set the bean id value converting if necessary. + */ + @Override + public void setBeanId(T bean, Object idValue) { + idBinder.convertSetId(idValue, (EntityBean) bean); + } + /** * Convert and set the id value. *

@@ -1563,6 +1716,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return idBinder.convertSetId(idValue, bean); } + @Override + public Property getProperty(String propName) { + return getBeanProperty(propName); + } + /** * Get a BeanProperty by its name. */ @@ -1585,6 +1743,27 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return c; } + /** + * Register all the assoc many properties on this bean that are not populated with the load context. + *

+ * This provides further lazy loading via the load context. + *

+ */ + public void lazyLoadRegister(String prefix, EntityBeanIntercept ebi, EntityBean bean, LoadContext loadContext) { + + // load the List/Set/Map proxy objects (deferred fetching of lists) + BeanPropertyAssocMany[] manys = propertiesMany(); + for (int i = 0; i < manys.length; i++) { + if (!ebi.isLoadedProperty(manys[i].getPropertyIndex())) { + BeanCollection ref = manys[i].createReferenceIfNull(bean); + if (ref != null && !ref.isRegisteredWithLoadContext()) { + String path = SplitName.add(prefix, manys[i].getName()); + loadContext.register(path, ref); + } + } + } + } + /** * Return true if the lazy loading property is a Many in which case just * define a Reference for the collection and not invoke a query. @@ -1626,16 +1805,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { // create a compound comparator based on the list of properties ElComparator[] comparators = new ElComparator[sortBy.size()]; - List sortProps = sortBy.getProperties(); + List sortProps = sortBy.getProperties(); for (int i = 0; i < sortProps.size(); i++) { - Property sortProperty = sortProps.get(i); + SortByClause.Property sortProperty = sortProps.get(i); comparators[i] = createPropertyComparator(sortProperty); } return new ElComparatorCompound(comparators); } - private ElComparator createPropertyComparator(Property sortProp) { + private ElComparator createPropertyComparator(SortByClause.Property sortProp) { ElPropertyValue elGetValue = getElGetValue(sortProp.getName()); @@ -1670,6 +1849,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return elGetValue; } + @Override + public ExpressionPath getExpressionPath(String path) { + return getElGetValue(path); + } + /** * Similar to ElPropertyValue but also uses foreign key shortcuts. *

@@ -2130,6 +2314,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return derivedTableJoins; } + @Override + public Collection allProperties() { + return propertiesAll(); + } + /** * Return a collection of all BeanProperty. This includes transient properties. */ @@ -2168,6 +2357,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } } + @Override public BeanProperty getIdProperty() { return idProperty; } @@ -2442,6 +2632,14 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return propertiesLocal; } + public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps); + } + + protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps); + } + public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { jsonHelp.jsonWrite(writeJson, bean, null); } @@ -2461,4 +2659,10 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException { return jsonHelp.jsonReadObject(jsonRead, path); } + + @Override + public T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException { + ReadJson jsonRead = new ReadJson(this, parser, readOptions, objectMapper); + return jsonRead(jsonRead, null); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 0e3830a1c..a189241a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -16,14 +16,14 @@ import java.util.Map; public class BeanDescriptorJsonHelp { private final BeanDescriptor desc; - + private final InheritInfo inheritInfo; - + public BeanDescriptorJsonHelp(BeanDescriptor desc) { this.desc = desc; this.inheritInfo = desc.inheritInfo; } - + public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException { writeJson.writeStartObject(key); @@ -46,12 +46,36 @@ public class BeanDescriptorJsonHelp { protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException { - WriteBean writeBean = writeJson.createWriteBean(desc, bean); writeBean.write(writeJson); } - - + + public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + + if (inheritInfo == null) { + jsonWriteDirtyProperties(writeJson, bean, dirtyProps); + + } else { + InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass()); + BeanDescriptor localDescriptor = localInheritInfo.getBeanDescriptor(); + localDescriptor.jsonWriteDirtyProperties(writeJson, bean, dirtyProps); + } + + } + + protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException { + + writeJson.writeStartObject(null); + // render the dirty properties + BeanProperty[] props = desc.propertiesNonTransient(); + for (int j = 0; j < props.length; j++) { + if (dirtyProps[props[j].getPropertyIndex()]) { + props[j].jsonWrite(writeJson, bean); + } + } + writeJson.writeEndObject(); + } + @SuppressWarnings("unchecked") public T jsonRead(ReadJson jsonRead, String path) throws IOException { @@ -66,14 +90,14 @@ public class BeanDescriptorJsonHelp { return null; } if (JsonToken.START_OBJECT != token) { - throw new JsonParseException("Unexpected token "+token+" - expecting start_object", parser.getCurrentLocation()); + throw new JsonParseException("Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation()); } } if (desc.inheritInfo == null) { return jsonReadObject(jsonRead, path); - } - + } + // check for the discriminator value to determine the correct sub type String discColumn = inheritInfo.getRoot().getDiscriminatorColumn(); @@ -81,8 +105,8 @@ public class BeanDescriptorJsonHelp { String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?"; throw new JsonParseException(msg, parser.getCurrentLocation()); } - - String propName = parser.getCurrentName(); + + String propName = parser.getCurrentName(); if (!propName.equalsIgnoreCase(discColumn)) { // just try to assume this is the correct bean type in the inheritance BeanProperty property = desc.getBeanProperty(propName); @@ -91,24 +115,24 @@ public class BeanDescriptorJsonHelp { property.jsonRead(jsonRead, bean); return jsonReadProperties(jsonRead, bean, path); } - String msg = "Error reading inheritance discriminator, expected property ["+discColumn+"] but got [" + propName + "] ?"; + String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?"; throw new JsonParseException(msg, parser.getCurrentLocation()); } - - String discValue = parser.nextTextValue(); - + + String discValue = parser.nextTextValue(); + // determine the sub type for this particular json object InheritInfo localInheritInfo = inheritInfo.readType(discValue); BeanDescriptor localDescriptor = localInheritInfo.getBeanDescriptor(); return (T) localDescriptor.jsonReadObject(jsonRead, path); } - + protected T jsonReadObject(ReadJson readJson, String path) throws IOException { EntityBean bean = desc.createEntityBean(); return jsonReadProperties(readJson, bean, path); } - + @SuppressWarnings("unchecked") protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException { @@ -117,7 +141,7 @@ public class BeanDescriptorJsonHelp { } // unmapped properties, send to JsonReadBeanVisitor later - Map unmappedProperties = null; + Map unmappedProperties = null; do { JsonParser parser = readJson.getParser(); @@ -141,16 +165,22 @@ public class BeanDescriptorJsonHelp { } else { throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation()); } - + } while (true); - // visit JsonReadBeanVisitor (if registered for this path) - readJson.beanVisitor(bean, unmappedProperties); - + Object contextBean = null; + Object id = desc.beanId(bean); + if (id != null) { + // check if the bean has already been loaded + contextBean = readJson.persistenceContextPutIfAbsent(id, bean, desc); + } + if (contextBean == null) { + readJson.beanVisitor(bean, unmappedProperties); + } if (path != null) { readJson.popPath(); } - return (T)bean; + return contextBean == null ? (T) bean : (T) contextBean; } - + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index d5722c0ab..fcf8c61bd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -52,6 +52,8 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader; import com.avaje.ebeaninternal.server.properties.BeanPropertyInfo; import com.avaje.ebeaninternal.server.properties.BeanPropertyInfoFactory; import com.avaje.ebeaninternal.server.properties.EnhanceBeanPropertyInfoFactory; +import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; +import com.avaje.ebeanservice.docstore.api.DocStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,12 +113,16 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final BeanManagerFactory beanManagerFactory; + private final ServerConfig serverConfig; + private final ChangeLogListener changeLogListener; private final ChangeLogRegister changeLogRegister; private final ChangeLogPrepare changeLogPrepare; + private final DocStoreFactory docStoreFactory; + private int enhancedClassCount; private final boolean updateChangesOnly; @@ -125,14 +131,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final String serverName; - private final ServerConfig serverConfig; - private Map, DeployBeanInfo> deplyInfoMap = new HashMap, DeployBeanInfo>(); private final Map, BeanTable> beanTableMap = new HashMap, BeanTable>(); private final Map> descMap = new HashMap>(); + private final Map> descQueueMap = new HashMap>(); + private final Map> beanManagerMap = new HashMap>(); private final Map>> tableToDescMap = new HashMap>>(); @@ -183,6 +189,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { this.serverConfig = config.getServerConfig(); this.serverName = InternString.intern(serverConfig.getName()); this.cacheManager = config.getCacheManager(); + this.docStoreFactory = config.getDocStoreFactory(); this.xmlConfig = config.getXmlConfig(); this.dbSequenceBatchSize = serverConfig.getDatabaseSequenceBatchSize(); this.backgroundExecutor = config.getBackgroundExecutor(); @@ -246,6 +253,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return serverConfig; } + @Override + public DocStoreBeanAdapter createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor deploy) { + return docStoreFactory.createAdapter(descriptor, deploy); + } + + public BeanDescriptor getBeanDescriptorByQueueId(String queueId) { + return descQueueMap.get(queueId); + } + @SuppressWarnings("unchecked") public BeanDescriptor getBeanDescriptor(Class entityType) { return (BeanDescriptor) descMap.get(entityType.getName()); @@ -433,6 +449,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap { d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap); } + // PASS 4: + // now initialise document mapping which needs target descriptors + for (BeanDescriptor d : descMap.values()) { + d.initialiseDocMapping(); + } + // create BeanManager for each non-embedded entity bean for (BeanDescriptor d : descMap.values()) { if (!d.isEmbedded()) { @@ -518,6 +540,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private void registerBeanDescriptor(BeanDescriptor desc) { descMap.put(desc.getBeanType().getName(), desc); + if (desc.isDocStoreMapped()) { + descQueueMap.put(desc.getDocStoreQueueId(), desc); + } } /** @@ -1050,7 +1075,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private DeployBeanInfo createDeployBeanInfo(Class beanClass) { - DeployBeanDescriptor desc = new DeployBeanDescriptor(beanClass); + DeployBeanDescriptor desc = new DeployBeanDescriptor(beanClass, serverConfig); desc.setUpdateChangesOnly(updateChangesOnly); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java index 28284cdf3..6db829d0b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java @@ -4,6 +4,8 @@ import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.config.EncryptKey; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; /** * Provides a method to find a BeanDescriptor. @@ -38,6 +40,13 @@ public interface BeanDescriptorMap { */ EncryptKey getEncryptKey(String tableName, String columnName); + /** + * Create a IdBinder for this bean property. + */ IdBinder createIdBinder(BeanProperty id); + /** + * Create a doc store specific adapter for this bean type. + */ + DocStoreBeanAdapter createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor deploy); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java index c29838389..15521d4a4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java @@ -166,6 +166,11 @@ public final class BeanFkeyProperty implements ElPropertyValue { throw new RuntimeException("ElPropertyDeploy only - not implemented"); } + @Override + public void set(Object bean, Object value) { + throw new RuntimeException("ElPropertyDeploy only - not implemented"); + } + public void elSetValue(EntityBean bean, Object value, boolean populate) { throw new RuntimeException("ElPropertyDeploy only - not implemented"); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index b97f7b4f5..9d88e6bdd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -5,6 +5,10 @@ import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.config.EncryptKey; import com.avaje.ebean.config.dbplatform.DbEncryptFunction; import com.avaje.ebean.config.dbplatform.DbType; +import com.avaje.ebeaninternal.server.type.ScalarTypeEnum; +import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping; +import com.avaje.ebean.plugin.Property; import com.avaje.ebean.text.StringParser; import com.avaje.ebeaninternal.server.core.InternString; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; @@ -24,6 +28,9 @@ import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.ScalarType; import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean; import com.avaje.ebeaninternal.util.ValueUtil; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; +import com.avaje.ebeanservice.docstore.api.support.DocStructure; import com.fasterxml.jackson.core.JsonToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +49,7 @@ import java.util.Map; * Description of a property of a bean. Includes its deployment information such * as database column mapping information. */ -public class BeanProperty implements ElPropertyValue { +public class BeanProperty implements ElPropertyValue, Property { private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class); @@ -196,6 +203,8 @@ public class BeanProperty implements ElPropertyValue { @SuppressWarnings("rawtypes") final ScalarType scalarType; + final DocPropertyOptions docOptions; + /** * The length or precision for DB column. */ @@ -313,6 +322,7 @@ public class BeanProperty implements ElPropertyValue { this.lob = isLobType(dbType); this.propertyType = deploy.getPropertyType(); this.field = deploy.getField(); + this.docOptions = deploy.getDocPropertyOptions(); this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null); this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn); @@ -414,6 +424,7 @@ public class BeanProperty implements ElPropertyValue { this.lob = isLobType(dbType); this.propertyType = source.getPropertyType(); this.field = source.getField(); + this.docOptions = source.docOptions; this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn); this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn); @@ -557,6 +568,11 @@ public class BeanProperty implements ElPropertyValue { } } + @Override + public boolean isMany() { + return false; + } + public boolean isAssignableFrom(Class type) { return owningType.isAssignableFrom(type); } @@ -670,6 +686,15 @@ public class BeanProperty implements ElPropertyValue { bean._ebean_getIntercept().setChangedProperty(propertyIndex); } + /** + * Set the changed value without invoking interception (lazy loading etc). + * Typically used to set generated values on update. + */ + public void setValueChanged(EntityBean bean, Object value) { + setValue(bean, value); + bean._ebean_getIntercept().setChangedProperty(propertyIndex); + } + /** * Set the value of the property without interception or * PropertyChangeSupport. @@ -716,6 +741,10 @@ public class BeanProperty implements ElPropertyValue { throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar"); } + public Object getVal(Object bean) { + return getValue((EntityBean)bean); + } + /** * Return the value of the property method. */ @@ -746,6 +775,14 @@ public class BeanProperty implements ElPropertyValue { return convertToLogicalType(value); } + @Override + public void set(Object bean, Object value) { + + // convert for Enums etc + Object logicalVal = convertToLogicalType(value); + elSetValue((EntityBean) bean, logicalVal, true); + } + public void elSetValue(EntityBean bean, Object value, boolean populate) { if (bean != null) { // Not using setValueIntercept at this stage @@ -1187,6 +1224,14 @@ public class BeanProperty implements ElPropertyValue { return name; } + /** + * Append this property to the document store based on includeByDefault setting. + */ + public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) { + if (includeByDefault) { + docStructure.addProperty(name); + } + } @SuppressWarnings(value = "unchecked") public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException { @@ -1263,4 +1308,28 @@ public class BeanProperty implements ElPropertyValue { map.put(propName, new ValuePair(newVal, oldVal)); } } + + /** + * Add to the document mapping if this property is included for this index. + */ + public void docStoreMapping(DocMappingBuilder mapping, String prefix) { + + if (mapping.includesProperty(prefix, name)) { + + DocPropertyType type = scalarType.getDocType(); + DocPropertyOptions options = docOptions.copy(); + if (DocPropertyType.STRING == type && isDocCode()) { + options.setCode(true); + } + + mapping.add(new DocPropertyMapping(name, type, options)); + } + } + + /** + * Return true if this should be treated as a 'code' (effectively not analysed). + */ + private boolean isDocCode() { + return id || scalarType instanceof ScalarTypeEnum; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java index 21341e33d..a787a55b4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -4,6 +4,12 @@ import java.util.ArrayList; import javax.persistence.PersistenceException; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; +import com.avaje.ebeanservice.docstore.api.support.DocStructure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +40,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { IdBinder targetIdBinder; InheritInfo targetInheritInfo; - + String targetIdProperty; /** @@ -59,6 +65,8 @@ public abstract class BeanPropertyAssoc extends BeanProperty { final String mappedBy; + final String docStoreDoc; + final String extraWhere; boolean saveRecurseSkippable; @@ -71,7 +79,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { this.extraWhere = InternString.intern(deploy.getExtraWhere()); this.beanTable = deploy.getBeanTable(); this.mappedBy = InternString.intern(deploy.getMappedBy()); - + this.docStoreDoc = deploy.getDocStoreDoc(); this.tableJoin = new TableJoin(deploy.getTableJoin()); this.targetType = deploy.getTargetType(); @@ -215,6 +223,63 @@ public abstract class BeanPropertyAssoc extends BeanProperty { return extraWhere; } + /** + * Return the elastic search doc for this embedded property. + */ + public String getDocStoreDoc() { + return docStoreDoc; + } + + /** + * Determine if and how the associated bean is included in the doc store document. + */ + @Override + public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) { + + String embeddedDoc = getDocStoreDoc(); + if (embeddedDoc == null) { + // not annotated so use include by default + // which is *ToOne included and *ToMany excluded + if (includeByDefault) { + docStoreIncludeByDefault(docStructure.doc()); + } + } else { + // explicitly annotated to be included + if (embeddedDoc.isEmpty()) { + embeddedDoc = "*"; + } + // add in a nested way + PathProperties embDoc = PathProperties.parse(embeddedDoc); + docStructure.addNested(name, embDoc); + } + } + + /** + * Include the property in the document store by default. + */ + protected void docStoreIncludeByDefault(PathProperties pathProps) { + pathProps.addToPath(null, name); + } + + @Override + public void docStoreMapping(DocMappingBuilder mapping, String prefix) { + + if (mapping.includesPath(prefix, name)) { + String fullName = SplitName.add(prefix, name); + + DocPropertyType type = isMany() ? DocPropertyType.LIST : DocPropertyType.OBJECT; + DocPropertyMapping nested = new DocPropertyMapping(name, type); + + mapping.push(nested); + targetDescriptor.docStoreMapping(mapping, fullName); + mapping.pop(); + + if (!nested.getChildren().isEmpty()) { + mapping.add(nested); + } + } + } + /** * Return true if this association is updateable. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 8cf145d66..a7514a363 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -1,11 +1,16 @@ package com.avaje.ebeaninternal.server.deploy; -import com.avaje.ebean.*; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Expression; +import com.avaje.ebean.Query; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; import com.avaje.ebean.bean.BeanCollectionAdd; import com.avaje.ebean.bean.BeanCollectionLoader; import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.deploy.id.ImportedId; @@ -91,6 +96,8 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { */ protected BeanPropertyAssocOne childMasterProperty; + private String childMasterIdProperty; + private boolean embeddedExportedProperties; private BeanCollectionHelp help; @@ -179,6 +186,21 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } } + /** + * Initialise after the target bean descriptors have been all set. + */ + public void initialisePostTarget() { + if (childMasterProperty != null) { + BeanProperty masterId = childMasterProperty.getTargetDescriptor().getIdProperty(); + childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName(); + } + } + + @Override + protected void docStoreIncludeByDefault(PathProperties pathProps) { + // by default not including "Many" properties in document store + } + /** * Add the bean to the appropriate collection on the parent bean. */ @@ -282,10 +304,29 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { return server.findIds(q, t); } + /** + * Add the loaded current bean to its associated parent. + */ + public void lazyLoadMany(EntityBean current) { + EntityBean parentBean = childMasterProperty.getValueAsEntityBean(current); + if (parentBean != null) { + addBeanToCollectionWithCreate(parentBean, current, true); + } + } + + public void addWhereParentIdIn(SpiQuery query, List parentIds, boolean useDocStore) { + if (useDocStore) { + // assumes the ManyToOne property is included + query.where().in(childMasterIdProperty, parentIds); + } else { + addWhereParentIdIn(query, parentIds); + } + } + /** * Add a where clause to the query for a given list of parent Id's. */ - public void addWhereParentIdIn(SpiQuery query, List parentIds) { + private void addWhereParentIdIn(SpiQuery query, List parentIds) { String tableAlias = manyToMany ? "int_." : "t0."; if (manyToMany) { @@ -450,6 +491,10 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix); } + @Override + public boolean isMany() { + return true; + } @Override public boolean isAssocId() { @@ -904,6 +949,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { jsonHelp.jsonRead(readJson, parentBean); } + @SuppressWarnings("unchecked") public void publishMany(EntityBean draft, EntityBean live) { // collections will not be null due to enhancement diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java new file mode 100644 index 000000000..93e2d0055 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java @@ -0,0 +1,73 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.annotation.DocCode; +import com.avaje.ebean.annotation.DocProperty; +import com.avaje.ebean.annotation.DocSortable; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions; + +/** + * The options for document property collected when reading deployment mapping. + */ +public class DeployDocPropertyOptions { + + private Boolean code; + + private Boolean sortable; + + private Boolean store; + + private Float boost; + + private String nullValue; + + /** + * Read the DocProperty deployment options. + */ + public void setDocProperty(DocProperty doc) { + code = checkDefault(doc.code()); + sortable = checkDefault(doc.sortable()); + store = checkDefault(doc.store()); + boost = checkDefault(doc.boost()); + nullValue = checkDefault(doc.nullValue()); + } + + /** + * Read the DocSortable deployment options. + */ + public void setDocSortable(DocSortable doc) { + sortable = Boolean.TRUE; + store = checkDefault(doc.store()); + boost = checkDefault(doc.boost()); + nullValue = checkDefault(doc.nullValue()); + } + + /** + * Read the DocCode deployment options. + */ + public void setDocCode(DocCode doc) { + code = Boolean.TRUE; + store = checkDefault(doc.store()); + boost = checkDefault(doc.boost()); + nullValue = checkDefault(doc.nullValue()); + } + + private String checkDefault(String s) { + return "".equals(s) ? null : s; + } + + private Float checkDefault(float boost) { + return (boost == 1) ? null : boost; + } + + private Boolean checkDefault(boolean store) { + return (store) ? Boolean.TRUE : null; + } + + /** + * Return the DocPropertyOptions with the collected options. + */ + public DocPropertyOptions create() { + return new DocPropertyOptions(code, sortable, store, boost, nullValue); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index e8564a6e0..cbc73a4ab 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -1,6 +1,9 @@ package com.avaje.ebeaninternal.server.deploy.meta; import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.annotation.DocStore; +import com.avaje.ebean.annotation.DocStoreEvent; +import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.TableName; import com.avaje.ebean.config.dbplatform.IdGenerator; import com.avaje.ebean.config.dbplatform.IdType; @@ -10,6 +13,7 @@ import com.avaje.ebean.event.BeanPersistListener; import com.avaje.ebean.event.BeanPostLoad; import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.changelog.ChangeLogFilter; +import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.server.core.CacheOptions; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController; @@ -53,6 +57,8 @@ public class DeployBeanDescriptor { private static final String I_SCALAOBJECT = "scala.ScalaObject"; + private final ServerConfig serverConfig; + /** * Map of BeanProperty Linked so as to preserve order. */ @@ -166,11 +172,31 @@ public class DeployBeanDescriptor { private String dbComment; + /** + * One of NONE, INDEX or EMBEDDED. + */ + private boolean docStoreMapped; + + private DocStore docStore; + + private PathProperties docStorePathProperties; + + private String docStoreQueueId; + + private String docStoreIndexName; + + private String docStoreIndexType; + + private DocStoreEvent docStorePersist; + private DocStoreEvent docStoreInsert; + private DocStoreEvent docStoreUpdate; + private DocStoreEvent docStoreDelete; /** * Construct the BeanDescriptor. */ - public DeployBeanDescriptor(Class beanType) { + public DeployBeanDescriptor(Class beanType, ServerConfig serverConfig) { + this.serverConfig = serverConfig; this.beanType = beanType; } @@ -234,6 +260,26 @@ public class DeployBeanDescriptor { return draftableElement; } + /** + * Read the top level doc store deployment information. + */ + public void readDocStore(DocStore docStore) { + + this.docStore = docStore; + docStoreMapped = true; + docStoreQueueId = docStore.queueId(); + docStoreIndexName = docStore.indexName(); + docStoreIndexType = docStore.indexType(); + docStorePersist = docStore.persist(); + docStoreInsert = docStore.insert(); + docStoreUpdate = docStore.update(); + docStoreDelete = docStore.delete(); + String doc = docStore.doc(); + if (doc != null && doc.length() > 0) { + docStorePathProperties = PathProperties.parse(doc); + } + } + public boolean isScalaObject() { Class[] interfaces = beanType.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { @@ -931,4 +977,61 @@ public class DeployBeanDescriptor { checkInheritance(parent); } } + + public PathProperties getDocStorePathProperties() { + return docStorePathProperties; + } + + /** + * Return true if this type is mapped for a doc store. + */ + public boolean isDocStoreMapped() { + return docStoreMapped; + } + + public String getDocStoreQueueId() { + return docStoreQueueId; + } + + public String getDocStoreIndexName() { + return docStoreIndexName; + } + + public String getDocStoreIndexType() { + return docStoreIndexType; + } + + public DocStore getDocStore() { + return docStore; + } + + /** + * Return the DocStore index behavior for bean inserts. + */ + public DocStoreEvent getDocStoreInsertEvent() { + return getDocStoreIndexEvent(docStoreInsert); + } + + /** + * Return the DocStore index behavior for bean updates. + */ + public DocStoreEvent getDocStoreUpdateEvent() { + return getDocStoreIndexEvent(docStoreUpdate); + } + + /** + * Return the DocStore index behavior for bean deletes. + */ + public DocStoreEvent getDocStoreDeleteEvent() { + return getDocStoreIndexEvent(docStoreDelete); + } + + private DocStoreEvent getDocStoreIndexEvent(DocStoreEvent mostSpecific) { + if (!docStoreMapped) { + return DocStoreEvent.IGNORE; + } + if (mostSpecific != DocStoreEvent.DEFAULT) return mostSpecific; + if (docStorePersist != DocStoreEvent.DEFAULT) return docStorePersist; + return serverConfig.getDocStoreConfig().getPersist(); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index 621af0612..088cf8d29 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -1,6 +1,9 @@ package com.avaje.ebeaninternal.server.deploy.meta; import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.DocCode; +import com.avaje.ebean.annotation.DocProperty; +import com.avaje.ebean.annotation.DocSortable; import com.avaje.ebean.annotation.SoftDelete; import com.avaje.ebean.annotation.UpdatedTimestamp; import com.avaje.ebean.annotation.WhenCreated; @@ -12,6 +15,7 @@ import com.avaje.ebean.config.dbplatform.DbEncrypt; import com.avaje.ebean.config.dbplatform.DbEncryptFunction; import com.avaje.ebeaninternal.server.core.InternString; import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.DeployDocPropertyOptions; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter; @@ -19,6 +23,7 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertySetter; import com.avaje.ebeaninternal.server.type.ScalarType; import com.avaje.ebeaninternal.server.type.ScalarTypeEnum; import com.avaje.ebeaninternal.server.type.ScalarTypeWrapper; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions; import javax.persistence.EmbeddedId; import javax.persistence.FetchType; @@ -159,6 +164,8 @@ public class DeployBeanProperty { */ private int dbType; + private DeployDocPropertyOptions docMapping = new DeployDocPropertyOptions(); + /** * The method used to read the property. */ @@ -908,4 +915,21 @@ public class DeployBeanProperty { public String getDbComment() { return dbComment; } + + public void setDocProperty(DocProperty docProperty) { + docMapping.setDocProperty(docProperty); + } + + public void setDocSortable(DocSortable docSortable) { + docMapping.setDocSortable(docSortable); + } + + public void setDocCode(DocCode docCode) { + docMapping.setDocCode(docCode); + } + + public DocPropertyOptions getDocPropertyOptions() { + return docMapping.create(); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index 123d27026..2a86d6304 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.deploy.meta; +import com.avaje.ebean.annotation.DocStoreEmbedded; import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo; import com.avaje.ebeaninternal.server.deploy.BeanTable; @@ -37,7 +38,9 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { * From the deployment mappedBy attribute. */ String mappedBy; - + + String docStoreDoc; + /** * Construct the property. */ @@ -126,4 +129,16 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { this.mappedBy = mappedBy; } } + + /** + * Set DocStoreEmbedded deployment information. + */ + public void setDocStoreEmbedded(DocStoreEmbedded embedded) { + docStoreDoc = embedded.doc(); + } + + public String getDocStoreDoc() { + return docStoreDoc; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java index 0e3e15ed4..f7421a77b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -11,6 +11,7 @@ import javax.persistence.UniqueConstraint; import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.annotation.CacheTuning; +import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.annotation.DbComment; import com.avaje.ebean.annotation.Draftable; import com.avaje.ebean.annotation.DraftableElement; @@ -166,6 +167,11 @@ public class AnnotationClass extends AnnotationParser { descriptor.setDbComment(comment.value()); } + DocStore docStore = cls.getAnnotation(DocStore.class); + if (docStore != null) { + descriptor.readDocStore(docStore); + } + UpdateMode updateMode = cls.getAnnotation(UpdateMode.class); if (updateMode != null) { descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index bb2777181..42bfa9f10 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -64,7 +64,7 @@ public class AnnotationFields extends AnnotationParser { for (DeployBeanProperty prop : descriptor.propertiesAll()) { if (prop instanceof DeployBeanPropertyAssoc) { - readAssocOne(prop); + readAssocOne((DeployBeanPropertyAssoc)prop); } else { readField(prop); } @@ -74,7 +74,7 @@ public class AnnotationFields extends AnnotationParser { /** * Read the Id marker annotations on EmbeddedId properties. */ - private void readAssocOne(DeployBeanProperty prop) { + private void readAssocOne(DeployBeanPropertyAssoc prop) { readJsonAnnotations(prop); @@ -91,6 +91,11 @@ public class AnnotationFields extends AnnotationParser { prop.setEmbedded(); } + DocStoreEmbedded docStoreEmbedded = get(prop, DocStoreEmbedded.class); + if (docStoreEmbedded != null) { + prop.setDocStoreEmbedded(docStoreEmbedded); + } + if (prop instanceof DeployBeanPropertyAssocOne) { if (prop.isId() && !prop.isEmbedded()) { prop.setEmbedded(); @@ -179,6 +184,19 @@ public class AnnotationFields extends AnnotationParser { } } + DocProperty docProperty = get(prop, DocProperty.class); + if (docProperty != null) { + prop.setDocProperty(docProperty); + } + DocSortable docSortable = get(prop, DocSortable.class); + if (docSortable != null) { + prop.setDocSortable(docSortable); + } + DocCode docCode = get(prop, DocCode.class); + if (docCode != null) { + prop.setDocCode(docCode); + } + if (get(prop, DbHstore.class) != null) { util.setDbHstore(prop); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java index 1b23ac731..98fd42a64 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java @@ -249,6 +249,11 @@ public class ElPropertyChain implements ElPropertyValue { return chain[last].elGetValue(prevBean); } + @Override + public void set(Object bean, Object value) { + elSetValue((EntityBean)bean, value, true); + } + public void elSetValue(EntityBean bean, Object value, boolean populate) { EntityBean prevBean = bean; diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java index 8cfd30604..8ba64b744 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java +++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.el; import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.plugin.ExpressionPath; import com.avaje.ebean.text.StringParser; /** @@ -9,7 +10,7 @@ import com.avaje.ebean.text.StringParser; * This can be used for local sorting and filtering. *

*/ -public interface ElPropertyValue extends ElPropertyDeploy { +public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath { /** * Return the Id values for the given bean value. diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java index f7a372b98..57acd03ce 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java @@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; @@ -26,6 +27,22 @@ class AllEqualsExpression extends NonPrepareExpression { return propName; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + context.writeBoolMustStart(); + for (Map.Entry entry : propMap.entrySet()) { + Object value = entry.getValue(); + String propName = entry.getKey(); + if (value == null) { + context.writeExists(false, propName); + } else { + context.writeTerm(propName, value); + } + } + context.writeBoolEnd(); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { if (propMap != null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java index 349baeace..871271d9b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java @@ -4,6 +4,8 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import java.io.IOException; + class BetweenExpression extends AbstractExpression { private static final long serialVersionUID = 2078918165221454910L; @@ -20,6 +22,11 @@ class BetweenExpression extends AbstractExpression { this.valueHigh = valHigh; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeRange(propName, Op.GT_EQ, valueLow, Op.LT_EQ, valueHigh); + } + @Override public void addBindValues(SpiExpressionRequest request) { request.addBindValue(valueLow); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java index 06fdba136..86a032d26 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -8,6 +8,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; +import java.io.IOException; + /** * Between expression where a value is between two properties. */ @@ -31,6 +33,15 @@ class BetweenPropertyExpression extends NonPrepareExpression { return propName; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + context.writeBoolMustStart(); + context.writeSimple(Op.LT_EQ, lowProperty, value); + context.writeSimple(Op.GT_EQ, highProperty, value); + context.writeBoolEnd(); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java index e4f32285a..eca057435 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -5,6 +5,8 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import java.io.IOException; + class CaseInsensitiveEqualExpression extends AbstractExpression { private static final long serialVersionUID = -6406036750998971064L; @@ -16,6 +18,21 @@ class CaseInsensitiveEqualExpression extends AbstractExpression { this.value = value.toLowerCase(); } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + String[] values = value.split(" "); + if (values.length == 1) { + context.writeMatch(propName, value); + } else { + context.writeBoolStart(true); + for (String val : values) { + context.writeMatch(propName, val); + } + context.writeBoolEnd(); + } + } + @Override public void addBindValues(SpiExpressionRequest request) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java index f085a92b2..4ae366686 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.expression; +import java.io.IOException; import java.util.ArrayList; import com.avaje.ebean.ExampleExpression; @@ -94,6 +95,17 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio } } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + if (!list.isEmpty()) { + context.writeBoolMustStart(); + for (SpiExpression expr : list) { + expr.writeElastic(context); + } + context.writeBoolEnd(); + } + } + @Override public SpiExpression copyForPlanKey() { return new DefaultExampleExpression(list); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java index 621f2c1cb..0226c93ea 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -306,7 +306,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression exists(Query subQuery) { - return new ExistsExpression((SpiQuery) subQuery, false); + return new ExistsQueryExpression((SpiQuery) subQuery, false); } /** @@ -314,7 +314,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression notExists(Query subQuery) { - return new ExistsExpression((SpiQuery) subQuery, true); + return new ExistsQueryExpression((SpiQuery) subQuery, true); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index fd8f841c0..47998d600 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java @@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; @@ -59,6 +60,33 @@ public class DefaultExpressionList implements SpiExpressionList { this(null, null, null, new ArrayList()); } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + writeElastic(context, null); + } + + public void writeElastic(ElasticExpressionContext context, SpiExpression idEquals) throws IOException { + + int size = list.size(); + if (size == 1 && idEquals == null) { + // only 1 expression - skip bool must + list.get(0).writeElastic(context); + } else if (size == 0 && idEquals != null) { + // only idEquals - skip bool must + idEquals.writeElastic(context); + } else { + // bool must wrap all the children + context.writeBoolMustStart(); + if (idEquals != null) { + idEquals.writeElastic(context); + } + for (int i = 0; i < size; i++) { + list.get(i).writeElastic(context); + } + context.writeBoolEnd(); + } + } + @Override public SpiExpressionList trimPath(int prefixTrim) { throw new RuntimeException("Only allowed on FilterExpressionList"); @@ -382,7 +410,7 @@ public class DefaultExpressionList implements SpiExpressionList { return false; } - DefaultExpressionList that = (DefaultExpressionList)other; + DefaultExpressionList that = (DefaultExpressionList) other; if (list.size() != that.list.size()) { return false; } @@ -396,7 +424,7 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public boolean isSameByBind(SpiExpression other) { - DefaultExpressionList that = (DefaultExpressionList)other; + DefaultExpressionList that = (DefaultExpressionList) other; if (list.size() != that.list.size()) { return false; } @@ -421,16 +449,16 @@ public class DefaultExpressionList implements SpiExpressionList { * Path does not exist - for the given path in a JSON document. */ @Override - public ExpressionList jsonNotExists(String propertyName, String path){ + public ExpressionList jsonNotExists(String propertyName, String path) { add(expr.jsonNotExists(propertyName, path)); return this; } - + /** * Equal to expression for the value at the given path in the JSON document. */ @Override - public ExpressionList jsonEqualTo(String propertyName, String path, Object value){ + public ExpressionList jsonEqualTo(String propertyName, String path, Object value) { add(expr.jsonEqualTo(propertyName, path, value)); return this; } @@ -439,7 +467,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Not Equal to - for the given path in a JSON document. */ @Override - public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val){ + public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val) { add(expr.jsonNotEqualTo(propertyName, path, val)); return this; } @@ -448,7 +476,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Greater than - for the given path in a JSON document. */ @Override - public ExpressionList jsonGreaterThan(String propertyName, String path, Object val){ + public ExpressionList jsonGreaterThan(String propertyName, String path, Object val) { add(expr.jsonGreaterThan(propertyName, path, val)); return this; } @@ -457,7 +485,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Greater than or equal to - for the given path in a JSON document. */ @Override - public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val){ + public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val) { add(expr.jsonGreaterOrEqual(propertyName, path, val)); return this; } @@ -466,7 +494,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Less than - for the given path in a JSON document. */ @Override - public ExpressionList jsonLessThan(String propertyName, String path, Object val){ + public ExpressionList jsonLessThan(String propertyName, String path, Object val) { add(expr.jsonLessThan(propertyName, path, val)); return this; } @@ -475,7 +503,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Less than or equal to - for the given path in a JSON document. */ @Override - public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val){ + public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val) { add(expr.jsonLessOrEqualTo(propertyName, path, val)); return this; } @@ -484,7 +512,7 @@ public class DefaultExpressionList implements SpiExpressionList { * Between - for the given path in a JSON document. */ @Override - public ExpressionList jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){ + public ExpressionList jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) { add(expr.jsonBetween(propertyName, path, lowerValue, upperValue)); return this; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java new file mode 100644 index 000000000..0f70fddce --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java @@ -0,0 +1,303 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebean.plugin.ExpressionPath; +import com.avaje.ebeaninternal.server.query.SplitName; +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Context for writing elastic search expressions. + */ +public class ElasticExpressionContext { + + public static final String MUST = "must"; + public static final String SHOULD = "should"; + public static final String MUST_NOT = "must_not"; + public static final String BOOL = "bool"; + public static final String TERM = "term"; + public static final String RANGE = "range"; + public static final String TERMS = "terms"; + public static final String IDS = "ids"; + public static final String VALUES = "values"; + public static final String PREFIX = "prefix"; + public static final String MATCH = "match"; + public static final String WILDCARD = "wildcard"; + public static final String EXISTS = "exists"; + public static final String FIELD = "field"; + + private final JsonGenerator json; + + private final BeanType desc; + + private String currentNestedPath; + + public ElasticExpressionContext(JsonGenerator json, BeanType desc) { + this.json = json; + this.desc = desc; + } + + /** + * Return the JsonGenerator. + */ + public JsonGenerator json() { + return json; + } + + /** + * Flush the JsonGenerator buffer. + */ + public void flush() throws IOException { + endNested(); + json.flush(); + } + + /** + * Return true if the path contains a many. + */ + public boolean containsMany(String path) { + ExpressionPath elPath = desc.getExpressionPath(path); + return elPath == null || elPath.containsMany(); + } + + /** + * Return an associated 'raw' property given the property name. + */ + private String rawProperty(String propertyName) { + return desc.docStore().rawProperty(propertyName); + } + + public void writeBoolStart(boolean conjunction) throws IOException { + writeBoolStart((conjunction) ? MUST : SHOULD); + } + + public void writeBoolMustStart() throws IOException { + writeBoolStart(MUST); + } + + public void writeBoolMustNotStart() throws IOException { + writeBoolStart(MUST_NOT); + } + + private void writeBoolStart(String type) throws IOException { + endNested(); + json.writeStartObject(); + json.writeObjectFieldStart(BOOL); + json.writeArrayFieldStart(type); + } + + public void writeBoolEnd() throws IOException { + json.writeEndArray(); + json.writeEndObject(); + json.writeEndObject(); + } + + public void writeTerm(String propertyName, Object value) throws IOException { + + writeRawType(TERM, rawProperty(propertyName), value); + } + + public void writeRange(String propertyName, String rangeType, Object value) throws IOException { + + prepareNestedPath(propertyName); + json.writeStartObject(); + json.writeObjectFieldStart(RANGE); + json.writeObjectFieldStart(rawProperty(propertyName)); + json.writeFieldName(rangeType); + json.writeObject(value); + json.writeEndObject(); + json.writeEndObject(); + json.writeEndObject(); + } + + public void writeRange(String propertyName, Op lowOp, Object valueLow, Op highOp, Object valueHigh) throws IOException { + + prepareNestedPath(propertyName); + json.writeStartObject(); + json.writeObjectFieldStart(RANGE); + json.writeObjectFieldStart(rawProperty(propertyName)); + json.writeFieldName(lowOp.docExp()); + json.writeObject(valueLow); + json.writeFieldName(highOp.docExp()); + json.writeObject(valueHigh); + json.writeEndObject(); + json.writeEndObject(); + json.writeEndObject(); + } + + public void writeTerms(String propertyName, Object[] values) throws IOException { + + prepareNestedPath(propertyName); + json.writeStartObject(); + json.writeObjectFieldStart(TERMS); + json.writeArrayFieldStart(rawProperty(propertyName)); + for (Object value : values) { + json.writeObject(value); + } + json.writeEndArray(); + json.writeEndObject(); + json.writeEndObject(); + } + + + public void writeIds(List idList) throws IOException { + + endNested(); + json.writeStartObject(); + json.writeObjectFieldStart(IDS); + json.writeArrayFieldStart(VALUES); + for (Object id : idList) { + json.writeObject(id); + } + json.writeEndArray(); + json.writeEndObject(); + json.writeEndObject(); + } + + public void writeId(Object value) throws IOException { + + List ids = new ArrayList(1); + ids.add(value); + writeIds(ids); + } + + public void writeSuffix(String propertyName, String value) { + throw new IllegalArgumentException("Not implemented yet. Could search for a mapped 'reversed' property and do prefix query"); + } + + public void writePrefix(String propertyName, String value) throws IOException { + // use analysed field + prepareNestedPath(propertyName); + writeRawType(PREFIX, propertyName, value); + } + + public void writeMatch(String propertyName, String value) throws IOException { + // use analysed field + prepareNestedPath(propertyName); + writeRawType(MATCH, propertyName, value); + } + + public void writeWildcard(String propertyName, String value) throws IOException { + prepareNestedPath(propertyName); + writeRawType(WILDCARD, propertyName, value); + } + + public void writeRaw(String jsonExpression) throws IOException { + json.writeRaw(jsonExpression); + } + + public void writeExists(boolean notNull, String propertyName) throws IOException { + + prepareNestedPath(propertyName); + if (!notNull) { + writeBoolMustNotStart(); + } + writeExists(propertyName); + if (!notNull) { + writeBoolEnd(); + } + } + + private void writeExists(String propertyName) throws IOException { + writeRawType(EXISTS, FIELD, propertyName); + } + + private void writeRawType(String type, String propertyName, Object value) throws IOException { + + prepareNestedPath(propertyName); + json.writeStartObject(); + json.writeObjectFieldStart(type); + json.writeFieldName(propertyName); + json.writeObject(value); + json.writeEndObject(); + json.writeEndObject(); + } + + + + public void writeSimple(Op type, String propertyName, Object value) throws IOException { + + prepareNestedPath(propertyName); + switch (type) { + case EQ: + writeTerm(propertyName, value); + break; + case NOT_EQ: + writeBoolMustNotStart(); + writeTerm(propertyName, value); + writeBoolEnd(); + break; + case EXISTS: + writeExists(true, propertyName); + break; + case NOT_EXISTS: + writeExists(false, propertyName); + break; + case BETWEEN: + throw new IllegalStateException("BETWEEN Not expected in SimpleExpression?"); + + default: + writeRange(propertyName, type.docExp(), value); + } + + } + + /** + * Write the query sort. + */ + public void writeOrderBy(OrderBy orderBy) throws IOException { + + if (orderBy != null && !orderBy.isEmpty()) { + json.writeArrayFieldStart("sort"); + for (OrderBy.Property property : orderBy.getProperties()) { + json.writeStartObject(); + json.writeObjectFieldStart(rawProperty(property.getProperty())); + json.writeStringField("order", property.isAscending() ? "asc" : "desc"); + json.writeEndObject(); + json.writeEndObject(); + } + json.writeEndArray(); + } + } + + private void prepareNestedPath(String propName) throws IOException { + ExpressionPath exprPath = desc.getExpressionPath(propName); + if (exprPath != null && exprPath.containsMany()) { + String[] manyPath = SplitName.splitBegin(propName); + startNested(manyPath[0]); + } else { + endNested(); + } + } + + private void startNested(String nestedPath) throws IOException { + + if (currentNestedPath != null) { + if (currentNestedPath.equals(nestedPath)) { + // just add to currentNestedPath + return; + } else { + endNested(); + } + } + currentNestedPath = nestedPath; + + json.writeStartObject(); + json.writeObjectFieldStart("nested"); + json.writeStringField("path", nestedPath); + json.writeFieldName("filter"); + } + + private void endNested() throws IOException { + if (currentNestedPath != null) { + currentNestedPath = null; + //json.writeEndObject(); + json.writeEndObject(); + json.writeEndObject(); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java similarity index 82% rename from src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java rename to src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java index dca741608..fdaf30d2e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.expression; +import java.io.IOException; import java.util.List; import com.avaje.ebean.event.BeanQueryRequest; @@ -13,7 +14,7 @@ import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; -public class ExistsExpression implements SpiExpression { +class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpression { private static final long serialVersionUID = 666990277309851644L; @@ -25,18 +26,23 @@ public class ExistsExpression implements SpiExpression { protected String sql; - public ExistsExpression(SpiQuery subQuery, boolean not) { + public ExistsQueryExpression(SpiQuery subQuery, boolean not) { this.subQuery = subQuery; this.not = not; } - ExistsExpression(boolean not, String sql , List bindParams) { + ExistsQueryExpression(boolean not, String sql , List bindParams) { this.not = not; this.sql = sql; this.bindParams = bindParams; this.subQuery = null; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + throw new IllegalStateException("Not supported"); + } + @Override public void prepareExpression(BeanQueryRequest request) { @@ -60,7 +66,7 @@ public class ExistsExpression implements SpiExpression { @Override public void queryPlanHash(HashQueryPlanBuilder builder) { - builder.add(ExistsExpression.class).add(not); + builder.add(ExistsQueryExpression.class).add(not); builder.add(sql).add(bindParams.size()); } @@ -90,11 +96,11 @@ public class ExistsExpression implements SpiExpression { @Override public boolean isSameByPlan(SpiExpression other) { - if (!(other instanceof ExistsExpression)) { + if (!(other instanceof ExistsQueryExpression)) { return false; } - ExistsExpression that = (ExistsExpression) other; + ExistsQueryExpression that = (ExistsQueryExpression) other; return this.sql.equals(that.sql) && this.not == that.not && this.bindParams.size() == that.bindParams.size(); @@ -102,7 +108,7 @@ public class ExistsExpression implements SpiExpression { @Override public boolean isSameByBind(SpiExpression other) { - ExistsExpression that = (ExistsExpression) other; + ExistsQueryExpression that = (ExistsQueryExpression) other; if (this.bindParams.size() != that.bindParams.size()) { return false; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java index 41aaec673..dd9ae9504 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java @@ -7,6 +7,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; + /** * Slightly redundant as Query.setId() ultimately also does the same job. */ @@ -20,6 +22,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression { this.value = value; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeId(value); + } + /** * Always returns false. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java index cf67e92c3..0fa095620 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java @@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import java.io.IOException; import java.util.List; /** @@ -27,6 +28,11 @@ public class IdInExpression extends NonPrepareExpression { public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeIds(idList); + } + @Override public void validate(SpiExpressionValidation validation) { // always valid diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java index 52af4e2d3..be11282eb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import java.io.IOException; import java.util.Collection; class InExpression extends AbstractExpression { @@ -28,6 +29,11 @@ class InExpression extends AbstractExpression { this.not = not; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeTerms(propName, values); + } + @Override public void addBindValues(SpiExpressionRequest request) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java index 5da4daee4..6914e69bf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -8,12 +8,13 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.query.CQuery; +import java.io.IOException; import java.util.List; /** * In expression using a sub query. */ -class InQueryExpression extends AbstractExpression { +class InQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression { private static final long serialVersionUID = 666990277309851644L; @@ -39,6 +40,11 @@ class InQueryExpression extends AbstractExpression { this.bindParams = bindParams; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + throw new IllegalStateException("Not supported"); + } + @Override public void prepareExpression(BeanQueryRequest request) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java index 0f14623b2..7249a1c17 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java @@ -4,6 +4,8 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import java.io.IOException; + /** * Generally speaking tests the value at a given path in the JSON document. *

@@ -57,6 +59,17 @@ class JsonPathExpression extends AbstractExpression { this.upperValue = upperValue; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + String fullName = propName + "." + path; + if (operator == Op.BETWEEN) { + context.writeRange(fullName, Op.GT_EQ, value, Op.LT_EQ, upperValue); + } else { + context.writeSimple(operator, fullName, value); + } + } + @Override public void queryPlanHash(HashQueryPlanBuilder builder) { builder.add(JsonPathExpression.class).add(propName).add(path).add(operator); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index 812540bf9..f25eefa0e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; import java.sql.Timestamp; import java.util.Collection; import java.util.List; @@ -98,6 +99,17 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr this.exprList = exprList; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + context.writeBoolStart(!disjunction); + List list = exprList.internalList(); + for (int i = 0; i < list.size(); i++) { + list.get(i).writeElastic(context); + } + context.writeBoolEnd(); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { @@ -126,8 +138,7 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr @Override public Junction add(Expression item) { - SpiExpression i = (SpiExpression) item; - exprList.add(i); + exprList.add(item); return this; } @@ -143,8 +154,7 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr List list = exprList.internalList(); for (int i = 0; i < list.size(); i++) { - SpiExpression item = list.get(i); - item.addBindValues(request); + list.get(i).addBindValues(request); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java index 56e58092f..907358ad0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -6,6 +6,8 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import java.io.IOException; + class LikeExpression extends AbstractExpression { private static final long serialVersionUID = -5398151809111172380L; @@ -23,6 +25,36 @@ class LikeExpression extends AbstractExpression { this.val = value; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + String paramVal = (caseInsensitive) ? val.toLowerCase() : val; + switch (type) { + case RAW: + context.writeWildcard(propName, paramVal); + break; + + case STARTS_WITH: + context.writePrefix(propName, paramVal); + break; + + case ENDS_WITH: + context.writeSuffix(propName, paramVal); + break; + + case CONTAINS: + context.writeMatch(propName, paramVal); + break; + + case EQUAL_TO: + context.writeTerm(propName, paramVal); + break; + + default: + throw new RuntimeException("LikeType " + type + " missed?"); + } + } + @Override public void addBindValues(SpiExpressionRequest request) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java index 977b63d5a..6f750f42c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java @@ -9,6 +9,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; + /** * A logical And or Or for joining two expressions. */ @@ -60,6 +62,16 @@ abstract class LogicExpression implements SpiExpression { this.expTwo = (SpiExpression) expTwo; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + boolean conjunction = joinType.equals(AND); + context.writeBoolStart(conjunction); + expOne.writeElastic(context); + expTwo.writeElastic(context); + context.writeBoolEnd(); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { expOne.containsMany(desc, manyWhereJoin); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java index a59ff8fbc..1f833feb6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java @@ -8,6 +8,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; + /** * Effectively an expression that has no effect. */ @@ -20,6 +22,10 @@ class NoopExpression implements SpiExpression { return this; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { // nothing to do diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java index b7d8867a7..60c3dfb82 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java @@ -9,6 +9,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; + final class NotExpression implements SpiExpression { private static final long serialVersionUID = 5648926732402355781L; @@ -22,6 +24,13 @@ final class NotExpression implements SpiExpression { this.exp = (SpiExpression) exp; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeBoolMustNotStart(); + exp.writeElastic(context); + context.writeBoolEnd(); + } + @Override public SpiExpression copyForPlanKey() { return new NotExpression(exp.copyForPlanKey()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java index 6ad5a58ea..06b55ca50 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java @@ -5,6 +5,8 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import java.io.IOException; + /** * Slightly redundant as Query.setId() ultimately also does the same job. @@ -20,6 +22,11 @@ class NullExpression extends AbstractExpression { this.notNull = notNull; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeExists(notNull, propName); + } + @Override public void addBindValues(SpiExpressionRequest request) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java index 77d06c557..c8add0bd4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java @@ -8,54 +8,56 @@ public enum Op { /** * Exists (JSON). */ - EXISTS(" is not null "), + EXISTS(" is not null ", ""), /** * Not Exists (JSON). */ - NOT_EXISTS(" is null "), + NOT_EXISTS(" is null ", ""), /** * Between (JSON). */ - BETWEEN(" between ? and ? "), + BETWEEN(" between ? and ? ", ""), /** * Equal to */ - EQ(" = ? "), + EQ(" = ? ", ""), /** * Not equal to. */ - NOT_EQ(" <> ? "), + NOT_EQ(" <> ? ", ""), /** - * * Less than. */ - LT(" < ? "), + LT(" < ? ", "lt"), /** * Less than or equal to. */ - LT_EQ(" <= ? "), + LT_EQ(" <= ? ", "lte"), /** * Greater than. */ - GT(" > ? "), + GT(" > ? ", "gt"), /** * Greater than or equal to. */ - GT_EQ(" >= ? "); + GT_EQ(" >= ? ", "gte"); final String exp; - Op(String exp) { + final String docExp; + + Op(String exp, String docExp) { this.exp = exp; + this.docExp = docExp; } /** @@ -64,4 +66,11 @@ public enum Op { public String bind() { return exp; } + + /** + * Return the doc store expression. + */ + public String docExp() { + return docExp; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java index 93261f6b2..6d4e0c27a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java @@ -7,6 +7,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.io.IOException; + class RawExpression extends NonPrepareExpression { private static final long serialVersionUID = 7973903141340334606L; @@ -20,6 +22,11 @@ class RawExpression extends NonPrepareExpression { this.values = values; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + context.writeRaw(sql); + } + @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java index 0b6a4f9a4..1093c4387 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -6,6 +6,8 @@ import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import java.io.IOException; + public class SimpleExpression extends AbstractExpression { private static final long serialVersionUID = -382881395755603790L; @@ -20,6 +22,16 @@ public class SimpleExpression extends AbstractExpression { this.value = value; } + @Override + public void writeElastic(ElasticExpressionContext context) throws IOException { + + if (type == Op.BETWEEN) { + throw new IllegalStateException("BETWEEN Not expected in SimpleExpression?"); + } + + context.writeSimple(type, propName, value); + } + public final String getPropName() { return propName; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java new file mode 100644 index 000000000..3849e06c7 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java @@ -0,0 +1,7 @@ +package com.avaje.ebeaninternal.server.expression; + +/** + * Marked interface for expressions unsupported in doc store. + */ +public interface UnsupportedDocStoreExpression { +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 966773413..59d7f39cb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -45,7 +45,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex protected void configureQuery(SpiQuery query, String lazyLoadProperty) { - parent.propagateQueryState(query); + parent.propagateQueryState(query, desc.isDocStoreMapped()); query.setParentNode(objectGraphNode); query.setLazyLoadProperty(lazyLoadProperty); if (queryProps != null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index cb71ec63e..7abcb9dfe 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.loadcontext; import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.CallStack; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.ObjectGraphOrigin; @@ -45,6 +46,7 @@ public class DLoadContext implements LoadContext { private final boolean disableLazyLoading; private final boolean disableReadAudit; private final boolean includeSoftDeletes; + protected final boolean useDocStore; /** * The path relative to the root of the object graph. @@ -59,6 +61,34 @@ public class DLoadContext implements LoadContext { private List secQuery; + /** + * Construct for use with JSON marshalling (doc store). + */ + public DLoadContext(BeanDescriptor rootDescriptor, PersistenceContext persistenceContext) { + + this.useDocStore = true; + this.rootDescriptor = rootDescriptor; + this.ebeanServer = rootDescriptor.getEbeanServer(); + this.persistenceContext = persistenceContext; + this.origin = initOrigin(); + this.defaultBatchSize = 100; + this.excludeBeanCache = false; + this.asDraft = false; + this.asOf = null; + this.readOnly = false; + this.disableLazyLoading = false; + this.disableReadAudit = false; + this.includeSoftDeletes = false; + this.relativePath = null; + this.useProfiling = false; + this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null); + } + + private ObjectGraphOrigin initOrigin() { + CallStack callStack = ebeanServer.createCallStack(); + return new ObjectGraphOrigin(0, callStack, rootDescriptor.getFullName()); + } + public DLoadContext(OrmQueryRequest request, SpiQuerySecondary secondaryQueries) { this.persistenceContext = request.getPersistenceContext(); @@ -67,6 +97,7 @@ public class DLoadContext implements LoadContext { this.rootDescriptor = request.getBeanDescriptor(); SpiQuery query = request.getQuery(); + this.useDocStore = query.isUseDocStore(); this.asOf = query.getAsOf(); this.asDraft = query.isAsDraft(); this.includeSoftDeletes = query.isIncludeSoftDeletes(); @@ -300,7 +331,10 @@ public class DLoadContext implements LoadContext { /** * Propagate the original query settings (draft, asOf etc) to the secondary queries. */ - public void propagateQueryState(SpiQuery query) { + public void propagateQueryState(SpiQuery query, boolean docStoreMapped) { + if (useDocStore && docStoreMapped) { + query.setUseDocStore(true); + } if (readOnly != null) { query.setReadOnly(readOnly); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java index 6ed015ce0..c484651a2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -56,7 +56,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex public void configureQuery(SpiQuery query) { - parent.propagateQueryState(query); + parent.propagateQueryState(query, desc.isDocStoreMapped()); query.setParentNode(objectGraphNode); if (queryProps != null) { queryProps.configureBeanQuery(query); @@ -129,6 +129,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex this.list = new ArrayList>(batchSize); } + @Override + public boolean isUseDocStore() { + return context.parent.useDocStore; + } + public int getBatchSize() { return batchSize; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java index fc9473ccc..f85168f26 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java @@ -1,6 +1,5 @@ package com.avaje.ebeaninternal.server.persist.dml; -import com.avaje.ebean.bean.EntityBean; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -17,7 +16,6 @@ import javax.persistence.OptimisticLockException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.ArrayList; /** * Base class for Handler implementations. @@ -48,7 +46,10 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { protected String sql; - protected ArrayList updateGenValues; + /** + * The generated value for the @Version property. Must be set after where clause is bound. + */ + protected Object versionValue; protected DmlHandler(PersistRequestBean persistRequest, boolean emptyStringToNull) { this.now = System.currentTimeMillis(); @@ -224,11 +225,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { *

*/ @Override - public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value) { - if (updateGenValues == null) { - updateGenValues = new ArrayList(); - } - updateGenValues.add(new UpdateGenValue(prop, bean, value)); + public void registerGeneratedVersion(Object versionValue) { + this.versionValue = versionValue; } /** @@ -236,11 +234,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { * clause has been bound. */ public void setUpdateGenValues() { - if (updateGenValues != null) { - for (int i = 0; i < updateGenValues.size(); i++) { - UpdateGenValue updGenVal = updateGenValues.get(i); - updGenVal.setValue(); - } + if (versionValue != null) { + persistRequest.setVersionValue(versionValue); } } @@ -282,30 +277,4 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { return stmt; } - /** - * Hold the values from GeneratedValue that need to be set to the bean - * property after the where clause has been built. - */ - private static final class UpdateGenValue { - - private final BeanProperty property; - - private final EntityBean bean; - - private final Object value; - - private UpdateGenValue(BeanProperty property, EntityBean bean, Object value) { - this.property = property; - this.bean = bean; - this.value = value; - } - - /** - * Set the value to the bean property. - */ - private void setValue() { - // support PropertyChangeSupport - property.setValueIntercept(bean, value); - } - } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java index fba5f5768..5d28a68af 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java @@ -42,13 +42,14 @@ public class BindablePropertyUpdateGenerated extends BindableProperty { // generated value should be the correct type request.bind(value, prop); - // only register the update value if it was included - // in the bean in the first place - if (request.getPersistRequest().isLoadedProperty(prop)) { - //if (request.isIncluded(prop)) { - // need to set the generated value to the bean later - // after the where clause has been generated - request.registerUpdateGenValue(prop, bean, value); + if (prop.isVersion()) { + if (request.getPersistRequest().isLoadedProperty(prop)) { + // set to the bean after the where clause has been generated + request.registerGeneratedVersion(value); + } + } else { + // @WhenModified set without invoking interception + prop.setValueChanged(bean, value); } } @@ -60,5 +61,4 @@ public class BindablePropertyUpdateGenerated extends BindableProperty { request.appendColumn(prop.getDbColumn()); } - } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java index 004cd5fd7..c034e5c78 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java @@ -48,7 +48,7 @@ public interface BindableRequest { * Register the value from a update GeneratedValue. This can only be set to * the bean property after the where clause has bean built. */ - void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value); + void registerGeneratedVersion(Object value); /** * Return the original PersistRequest. diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 346d83ab8..cbd2c2156 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -194,7 +194,7 @@ public class CQuery implements DbReadContext, CancelableQuery { this.queryPlan = queryPlan; this.query = request.getQuery(); this.queryMode = query.getMode(); - this.lazyLoadManyProperty = query.getLazyLoadForParentsProperty(); + this.lazyLoadManyProperty = query.getLazyLoadMany(); this.readOnly = request.isReadOnly(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index 285f412a1..93eaf9087 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -259,7 +259,7 @@ public class SqlTreeBuilder { buildExtraJoins(desc, myList); // Optional many property for lazy loading query - BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty(); + BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany(); boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct()); return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query), disableLazyLoad); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index c48697455..b64dacb58 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -232,7 +232,6 @@ public class SqlTreeNodeBean implements SqlTreeNode { PersistenceContext persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext(); - boolean newBean = false; if (readId) { Object id = localIdBinder.readSet(ctx, localBean); if (id == null) { @@ -243,7 +242,6 @@ public class SqlTreeNodeBean implements SqlTreeNode { contextBean = (EntityBean) persistenceContext.putIfAbsent(id, localBean); if (contextBean == null) { // bean just added to the persistenceContext - newBean = true; contextBean = localBean; } else { // bean already exists in persistenceContext diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 00354d496..3eff78a71 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -10,6 +10,8 @@ import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebean.event.readaudit.ReadEvent; import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.text.json.JsonContext; import com.avaje.ebeaninternal.api.BindParams; import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.HashQuery; @@ -25,11 +27,15 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; import com.avaje.ebeaninternal.server.deploy.TableJoin; -import com.avaje.ebeaninternal.server.expression.DefaultExpressionList; +import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext; import com.avaje.ebeaninternal.server.expression.SimpleExpression; import com.avaje.ebeaninternal.server.query.CancelableQuery; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionList; +import com.fasterxml.jackson.core.JsonGenerator; import javax.persistence.PersistenceException; +import java.io.IOException; +import java.io.StringWriter; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Iterator; @@ -42,29 +48,27 @@ import java.util.Set; */ public class DefaultOrmQuery implements SpiQuery { - private static final long serialVersionUID = 6838006264714672460L; - private final Class beanType; - private transient final EbeanServer server; + private final BeanDescriptor beanDescriptor; - private transient BeanCollectionTouched beanCollectionTouched; + private final EbeanServer server; - private transient final ExpressionFactory expressionFactory; + private BeanCollectionTouched beanCollectionTouched; + + private final ExpressionFactory expressionFactory; /** * For lazy loading of ManyToMany we need to add a join to the intersection table. This is that * join to the intersection table. */ - private transient TableJoin includeTableJoin; + private TableJoin includeTableJoin; - private transient ProfilingListener profilingListener; - - private transient BeanDescriptor beanDescriptor; + private ProfilingListener profilingListener; private boolean cancelled; - private transient CancelableQuery cancelableQuery; + private CancelableQuery cancelableQuery; /** * The name of the query. @@ -235,14 +239,17 @@ public class DefaultOrmQuery implements SpiQuery { */ private CQueryPlanKey queryPlanKey; - private transient PersistenceContext persistenceContext; + private PersistenceContext persistenceContext; private ManyWhereJoins manyWhereJoins; private RawSql rawSql; - public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, String query) { - this.beanType = beanType; + private boolean useDocStore; + + public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory, String query) { + this.beanDescriptor = desc; + this.beanType = desc.getBeanType(); this.server = server; this.expressionFactory = expressionFactory; this.detail = new OrmQueryDetail(); @@ -255,10 +262,11 @@ public class DefaultOrmQuery implements SpiQuery { /** * Additional supply a query which is parsed. */ - public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, + public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory, DeployNamedQuery namedQuery) throws PersistenceException { - this.beanType = beanType; + this.beanDescriptor = desc; + this.beanType = desc.getBeanType(); this.server = server; this.expressionFactory = expressionFactory; this.detail = new OrmQueryDetail(); @@ -282,6 +290,88 @@ public class DefaultOrmQuery implements SpiQuery { } } + public String asElasticQuery() { + + StringWriter sw = new StringWriter(200); + JsonContext json = server.json(); + + JsonGenerator generator = json.createGenerator(sw); + + BeanType beanType = server.getPluginApi().getBeanType(this.beanType); + ElasticExpressionContext context = new ElasticExpressionContext(generator, beanType); + + try { + writeElastic(context); + context.flush(); + return sw.toString(); + + } catch (IOException e) { + throw new PersistenceIOException(e); + } + } + + public void writeElastic(ElasticExpressionContext context) throws IOException { + + JsonGenerator json = context.json(); + json.writeStartObject(); + if (firstRow > 0) { + json.writeNumberField("from", firstRow); + } + if (maxRows > 0) { + json.writeNumberField("size", maxRows); + } + + detail.writeElastic(context); + context.writeOrderBy(orderBy); + + json.writeFieldName("query"); + json.writeStartObject(); + + SpiExpression idEquals = null; + if (id != null) { + idEquals = (SpiExpression)expressionFactory.idEq(id); + } + + boolean hasWhere = (whereExpressions != null && !whereExpressions.isEmpty()); + if (idEquals != null || hasWhere) { + json.writeFieldName("filtered"); + json.writeStartObject(); + json.writeFieldName("filter"); + if (hasWhere) { + whereExpressions.writeElastic(context, idEquals); + } else { + idEquals.writeElastic(context); + } + json.writeEndObject(); + } else { + json.writeObjectFieldStart("match_all"); + json.writeEndObject(); + } + json.writeEndObject(); + json.writeEndObject(); + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return beanDescriptor; + } + + @Override + public boolean isAutoTunable() { + return beanDescriptor.isAutoTunable() && !isSqlSelect(); + } + + @Override + public Query setUseDocStore(boolean useDocStore) { + this.useDocStore = useDocStore; + return this; + } + + @Override + public boolean isUseDocStore() { + return useDocStore; + } + @Override public Query apply(FetchPath fetchPath) { fetchPath.apply(this); @@ -337,13 +427,6 @@ public class DefaultOrmQuery implements SpiQuery { return this; } - /** - * Set the BeanDescriptor for the root type of this query. - */ - public void setBeanDescriptor(BeanDescriptor beanDescriptor) { - this.beanDescriptor = beanDescriptor; - } - public RawSql getRawSql() { return rawSql; } @@ -559,7 +642,7 @@ public class DefaultOrmQuery implements SpiQuery { public DefaultOrmQuery copy(EbeanServer server) { - DefaultOrmQuery copy = new DefaultOrmQuery(beanType, server, expressionFactory, (String) null); + DefaultOrmQuery copy = new DefaultOrmQuery(beanDescriptor, server, expressionFactory, (String) null); copy.name = name; copy.includeTableJoin = includeTableJoin; copy.profilingListener = profilingListener; @@ -669,7 +752,7 @@ public class DefaultOrmQuery implements SpiQuery { } @Override - public BeanPropertyAssocMany getLazyLoadForParentsProperty() { + public BeanPropertyAssocMany getLazyLoadMany() { return lazyLoadForParentsProperty; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java index 47fb077c6..36a331012 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -6,9 +6,12 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext; import com.avaje.ebeaninternal.server.query.SplitName; +import com.fasterxml.jackson.core.JsonGenerator; import javax.persistence.PersistenceException; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -498,4 +501,67 @@ public class OrmQueryDetail implements Serializable { public Set getFetchPaths() { return fetchPaths.keySet(); } + + /** + * Write the Elastic search source include and fields if necessary. + *

+ * Fetch all property is put into includes. + * Fetch on 'many' path is put into includes. + * Fetch on 'one' paths and root path are put into fields. + *

+ */ + public void writeElastic(ElasticExpressionContext context) throws IOException { + + Set includes = new LinkedHashSet(); + Set fields = new LinkedHashSet(); + + for (Map.Entry entry : fetchPaths.entrySet()) { + + String path = entry.getKey(); + OrmQueryProperties value = entry.getValue(); + if (value.allProperties()) { + includes.add(path + ".*"); + } else if (context.containsMany(path)) { + for (String propName : value.getIncluded()) { + includes.add(path + "." + propName); + } + } else { + for (String propName : value.getIncluded()) { + fields.add(path + "." + propName); + } + } + } + + if (hasSelectClause()) { + Set included = baseProps.getIncluded(); + if (included != null) { + for (String propName : included) { + fields.add(propName); + } + } + } + + if (!includes.isEmpty()) { + JsonGenerator json = context.json(); + json.writeFieldName("_source"); + json.writeStartObject(); + json.writeFieldName("include"); + json.writeStartArray(); + for (String propName : includes) { + json.writeString(propName); + } + json.writeEndArray(); + json.writeEndObject(); + } + + if (!fields.isEmpty()) { + JsonGenerator json = context.json(); + json.writeFieldName("fields"); + json.writeStartArray(); + for (String propName : fields) { + json.writeString(propName); + } + json.writeEndArray(); + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java new file mode 100644 index 000000000..8099e9357 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java @@ -0,0 +1,51 @@ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.PersistenceIOException; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.text.json.JsonBeanReader; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; + +/** + * A 'context' for reading entity beans from JSON. + *

+ * This is used such that a load context and persistence context can be used to span multiple marshalling requests. + *

+ */ +public class DJsonBeanReader implements JsonBeanReader { + + private final BeanDescriptor desc; + + private final ReadJson readJson; + + public DJsonBeanReader(BeanDescriptor desc, ReadJson readJson) { + this.desc = desc; + this.readJson = readJson; + } + + @Override + public void persistenceContextPut(Object beanId, T currentBean) { + readJson.persistenceContextPut(beanId, currentBean); + } + + @Override + public PersistenceContext getPersistenceContext() { + return readJson.getPersistenceContext(); + } + + @Override + public T read() { + try { + return desc.jsonRead(readJson, null); + } catch (IOException e) { + throw new PersistenceIOException(e); + } + } + + @Override + public JsonBeanReader forJson(JsonParser moreJson) { + return new DJsonBeanReader(desc, readJson.forJson(moreJson)); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java index 3968b3ecf..78b88fd1b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java @@ -106,15 +106,23 @@ public class DJsonContext implements JsonContext { public T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { - ReadJson readJson = new ReadJson(parser, options, determineObjectMapper(options)); + BeanDescriptor desc = getDescriptor(cls); + ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options)); try { - BeanDescriptor d = getDescriptor(cls); - return d.jsonRead(readJson, null); + return desc.jsonRead(readJson, null); } catch (IOException e) { throw new JsonIOException(e); } } + @Override + public DJsonBeanReader createBeanReader(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { + + BeanDescriptor desc = getDescriptor(cls); + ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options)); + return new DJsonBeanReader(desc, readJson); + } + public List toList(Class cls, String json) throws JsonIOException { return toList(cls, new StringReader(json)); } @@ -138,9 +146,9 @@ public class DJsonContext implements JsonContext { public List toList(Class cls, JsonParser src, JsonReadOptions options) throws JsonIOException { - ReadJson readJson = new ReadJson(src, options, determineObjectMapper(options)); + BeanDescriptor desc = getDescriptor(cls); + ReadJson readJson = new ReadJson(desc, src, options, determineObjectMapper(options)); try { - BeanDescriptor d = getDescriptor(cls); List list = new ArrayList(); @@ -153,7 +161,7 @@ public class DJsonContext implements JsonContext { } do { - T bean = d.jsonRead(readJson, null); + T bean = desc.jsonRead(readJson, null); if (bean == null) { break; } else { diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java index c66f209fe..8fbfa1035 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java @@ -1,7 +1,14 @@ package com.avaje.ebeaninternal.server.text.json; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.text.json.JsonReadBeanVisitor; import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.loadcontext.DLoadContext; +import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; @@ -14,34 +21,120 @@ import java.util.Map; */ public class ReadJson { + private final BeanDescriptor rootDesc; + /** * Jackson parser. */ - final JsonParser parser; + private final JsonParser parser; /** * Stack of the path - used to find the appropriate JsonReadBeanVisitor. */ - final PathStack pathStack; + private final PathStack pathStack; /** * Map of the JsonReadBeanVisitor keyed by path. */ - final Map> visitorMap; + private final Map> visitorMap; - final Object objectMapper; + private final Object objectMapper; + + private final PersistenceContext persistenceContext; + + private final LoadContext loadContext; /** * Construct with parser and readOptions. */ - public ReadJson(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) { + public ReadJson(BeanDescriptor desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper) { + this.rootDesc = desc; this.parser = parser; this.objectMapper = objectMapper; + this.persistenceContext = initPersistenceContext(readOptions); + this.loadContext = initLoadContext(desc, readOptions); // only create visitorMap, pathStack if needed ... this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap(); - this.pathStack = (visitorMap == null) ? null : new PathStack(); + this.pathStack = (visitorMap == null && loadContext == null) ? null : new PathStack(); + } + + /** + * Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance. + */ + private ReadJson(JsonParser moreJson, ReadJson source) { + this.parser = moreJson; + this.rootDesc = source.rootDesc; + this.pathStack = source.pathStack; + this.visitorMap = source.visitorMap; + this.objectMapper = source.objectMapper; + this.persistenceContext = source.persistenceContext; + this.loadContext = source.loadContext; + } + + private LoadContext initLoadContext(BeanDescriptor desc, JsonReadOptions readOptions) { + if (readOptions != null && readOptions.isEnableLazyLoading()) { + return new DLoadContext(desc, persistenceContext); + } + return null; + } + + private PersistenceContext initPersistenceContext(JsonReadOptions readOptions) { + if (readOptions != null && readOptions.getPersistenceContext() != null) { + return readOptions.getPersistenceContext(); + } + return new DefaultPersistenceContext(); + } + + /** + * Return the persistence context being used if any. + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + /** + * Return a new instance of ReadJson using the existing context but with a new JsonParser. + */ + public ReadJson forJson(JsonParser moreJson) { + return new ReadJson(moreJson, this); + } + + /** + * Add the bean to the persistence context. + */ + public void persistenceContextPut(Object beanId, T currentBean) { + + persistenceContextPutIfAbsent(beanId, (EntityBean)currentBean, rootDesc); + } + + /** + * Put the bean into the persistence context. If there is already a matching bean in the + * persistence context then return that instance else return null. + */ + public Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor beanDesc) { + + if (persistenceContext == null) { + // no persistenceContext means no lazy loading either + return null; + } + + Object contextBean = persistenceContext.putIfAbsent(id, bean); + if (contextBean == null) { + if (loadContext != null) { + EntityBeanIntercept ebi = bean._ebean_getIntercept(); + if (ebi.isPartial()) { + // register for further lazy loading + String path = pathStack.peekWithNull(); + loadContext.register(path, ebi); + beanDesc.lazyLoadRegister(path, ebi, bean, loadContext); + } + ebi.setLoaded(); + } + return null; + } + return contextBean; } /** @@ -110,4 +203,5 @@ public class ReadJson { public Object readValueUsingObjectMapper(Class propertyType) throws IOException { return getObjectMapper().readValue(parser, propertyType); } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java index 11f423800..301a76cc9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.transaction; import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.core.BootupClasses; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; @@ -16,10 +17,10 @@ import java.sql.Connection; */ public class AutoCommitTransactionManager extends TransactionManager { - public AutoCommitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, - ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { - - super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses); + public AutoCommitTransactionManager(ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, + DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { + + super(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java index 92ed02e53..68c0a0a39 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -23,7 +23,7 @@ import java.util.Set; *

* Duplicate beans are ones having the same type and unique id value. These are * considered duplicates and replaced by the bean instance that was already - * loaded into the PersistanceContext. + * loaded into the PersistenceContext. *

*/ public final class DefaultPersistenceContext implements PersistenceContext { @@ -36,13 +36,13 @@ public final class DefaultPersistenceContext implements PersistenceContext { private final Monitor monitor = new Monitor(); /** - * Create a new PersistanceContext. + * Create a new PersistenceContext. */ public DefaultPersistenceContext() { } /** - * Set an object into the PersistanceContext. + * Set an object into the PersistenceContext. */ public void put(Object id, Object bean) { synchronized (monitor) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java index 9f56a3bfc..1f5fe4e61 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java @@ -9,6 +9,10 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.avaje.ebean.annotation.DocStoreEvent; +import com.avaje.ebeanservice.docstore.api.support.DocStoreDeleteEvent; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + /** * Beans deleted by Id used for updating L2 Cache. */ @@ -72,5 +76,28 @@ public final class DeleteByIdMap { return r; } - + /** + * Add the deletes to the DocStoreUpdates. + */ + public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates, DocStoreEvent txnIndexMode) { + for (BeanPersistIds deleteIds : beanMap.values()) { + BeanDescriptor desc = deleteIds.getBeanDescriptor(); + DocStoreEvent docStoreEvent = desc.getDocStoreEvent(PersistRequest.Type.DELETE, txnIndexMode); + if (DocStoreEvent.IGNORE != docStoreEvent) { + // Add to queue or bulk update entries + boolean queue = (DocStoreEvent.QUEUE == docStoreEvent); + String queueId = desc.getDocStoreQueueId(); + List idValues = deleteIds.getDeleteIds(); + if (idValues != null) { + for (int i = 0; i < idValues.size(); i++) { + if (queue) { + docStoreUpdates.queueDelete(queueId, idValues.get(i)); + } else { + docStoreUpdates.addDelete(new DocStoreDeleteEvent(desc, idValues.get(i))); + } + } + } + } + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java index 25b25e221..54005421e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java @@ -4,6 +4,7 @@ import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.core.BootupClasses; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; @@ -16,10 +17,10 @@ import java.sql.Connection; */ public class ExplicitTransactionManager extends TransactionManager { - public ExplicitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, - ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { + public ExplicitTransactionManager(ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, + DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { - super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses); + super(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java index 1a61d739c..69ddf2d12 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.transaction; import com.avaje.ebean.TransactionCallback; +import com.avaje.ebean.annotation.DocStoreEvent; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.config.PersistBatch; import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly; @@ -142,6 +143,14 @@ public class JdbcTransaction implements SpiTransaction { protected TChangeLogHolder changeLogHolder; + /** + * The mode for updating doc store indexes for this transaction. + * Only set when you want to override the default behavior. + */ + protected DocStoreEvent docStoreUpdateMode; + + protected int docStoreBulkBatchSize; + /** * Create a new JdbcTransaction. */ @@ -272,6 +281,25 @@ public class JdbcTransaction implements SpiTransaction { } } + @Override + public int getDocStoreBulkBatchSize() { + return docStoreBulkBatchSize; + } + + @Override + public void setDocStoreUpdateBatchSize(int docStoreBulkBatchSize) { + this.docStoreBulkBatchSize = docStoreBulkBatchSize; + } + + public DocStoreEvent getDocStoreUpdateMode() { + return docStoreUpdateMode; + } + + @Override + public void setDocStoreUpdateMode(DocStoreEvent docStoreUpdateMode) { + this.docStoreUpdateMode = docStoreUpdateMode; + } + @Override public List getDerivedRelationship(Object bean) { if (derivedRelMap == null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java index b8da478cb..faa96529a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java @@ -1,8 +1,11 @@ package com.avaje.ebeaninternal.server.transaction; +import com.avaje.ebean.annotation.DocStoreEvent; +import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.api.TransactionEvent; import com.avaje.ebeaninternal.api.TransactionEventTable; import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; @@ -37,14 +40,20 @@ public final class PostCommitProcessing { private final DeleteByIdMap deleteByIdMap; + private final DocStoreEvent txnDocStoreMode; + + private final int txnDocStoreBatchSize; + /** - * Create for a TransactionManager and event. + * Create for an external modification. */ public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) { this.clusterManager = clusterManager; this.manager = manager; this.serverName = manager.getServerName(); + this.txnDocStoreMode = DocStoreEvent.IGNORE; + this.txnDocStoreBatchSize = 0; this.event = event; this.deleteByIdMap = event.getDeleteByIdMap(); this.persistBeanRequests = event.getPersistRequestBeans(); @@ -52,6 +61,23 @@ public final class PostCommitProcessing { this.remoteTransactionEvent = createRemoteTransactionEvent(); } + /** + * Create for a transaction. + */ + public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) { + + this.clusterManager = clusterManager; + this.manager = manager; + this.serverName = manager.getServerName(); + this.txnDocStoreMode = transaction.getDocStoreUpdateMode(); + this.txnDocStoreBatchSize = transaction.getDocStoreBulkBatchSize(); + this.event = transaction.getEvent(); + this.deleteByIdMap = event.getDeleteByIdMap(); + this.persistBeanRequests = event.getPersistRequestBeans(); + this.beanPersistIdMap = createBeanPersistIdMap(); + this.remoteTransactionEvent = createRemoteTransactionEvent(); + } + public void notifyLocalCacheIndex() { // notify cache with bulk insert/update/delete statements @@ -76,6 +102,33 @@ public final class PostCommitProcessing { } } + /** + * Process any document store updates. + */ + protected void processDocStoreUpdates() { + + if (isDocStoreUpdate()) { + // collect 'bulk update' and 'queue' events + DocStoreUpdates docStoreUpdates = new DocStoreUpdates(); + event.addDocStoreUpdates(docStoreUpdates); + if (deleteByIdMap != null) { + deleteByIdMap.addDocStoreUpdates(docStoreUpdates, txnDocStoreMode); + } + + if (!docStoreUpdates.isEmpty()) { + // send to docstore / ElasticSearch and/or queue + manager.processDocStoreUpdates(docStoreUpdates, txnDocStoreBatchSize); + } + } + } + + /** + * Return true if updates to the document store occur for this transaction. + */ + private boolean isDocStoreUpdate() { + return manager.isDocStoreActive() && (txnDocStoreMode == null || txnDocStoreMode != DocStoreEvent.IGNORE); + } + public void notifyCluster() { if (remoteTransactionEvent != null && !remoteTransactionEvent.isEmpty()) { // send the interesting events to the cluster @@ -91,6 +144,7 @@ public final class PostCommitProcessing { return new Runnable() { public void run() { localPersistListenersNotify(); + processDocStoreUpdates(); } }; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java index b7e7e3b50..239aa0ffa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java @@ -17,6 +17,8 @@ import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.core.BootupClasses; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,6 +72,13 @@ public class TransactionManager { protected final String serverName; + protected final boolean docStoreActive; + + /** + * The elastic search index update processor. + */ + protected final DocStoreUpdateProcessor docStoreUpdateProcessor; + protected final PersistBatch persistBatch; protected final PersistBatch persistBatchOnCascade; @@ -97,7 +106,7 @@ public class TransactionManager { /** * Create the TransactionManager */ - public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config, + public TransactionManager(ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) { this.persistBatch = config.getPersistBatch(); @@ -109,11 +118,12 @@ public class TransactionManager { this.serverName = config.getName(); this.backgroundExecutor = backgroundExecutor; this.dataSource = config.getDataSource(); + this.docStoreActive = config.getDocStoreConfig().isActive(); + this.docStoreUpdateProcessor = docStoreUpdateProcessor; this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners()); List transactionEventListeners = bootupClasses.getTransactionEventListeners(); - this.transactionEventListeners = transactionEventListeners.toArray(new - TransactionEventListener[transactionEventListeners.size()]); + this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]); this.prefix = ""; this.externalTransPrefix = "e"; @@ -136,6 +146,10 @@ public class TransactionManager { } } + public boolean isDocStoreActive() { + return docStoreActive; + } + public BeanDescriptorManager getBeanDescriptorManager() { return beanDescriptorManager; } @@ -391,7 +405,7 @@ public class TransactionManager { TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit"); } - PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction.getEvent()); + PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction); postCommit.notifyLocalCacheIndex(); postCommit.notifyCluster(); @@ -454,6 +468,13 @@ public class TransactionManager { } } + /** + * Process the docstore / ElasticSearch updates. + */ + public void processDocStoreUpdates(DocStoreUpdates docStoreUpdates, int bulkBatchSize) { + docStoreUpdateProcessor.process(docStoreUpdates, bulkBatchSize); + } + /** * Prepare and then send/log the changeSet. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java index c1f675bf0..ef7255fdc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java @@ -11,126 +11,131 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue; * This is used for non-scalar properties of a Compound Value Object. These only * occur in nested compound types. *

- * + * * @author rbygrave */ public class CtCompoundPropertyElAdapter implements ElPropertyValue { - private final CtCompoundProperty prop; + private final CtCompoundProperty prop; - private int deployOrder; - - public CtCompoundPropertyElAdapter(CtCompoundProperty prop) { - this.prop = prop; - } - - public void setDeployOrder(int deployOrder) { - this.deployOrder = deployOrder; - } + private int deployOrder; - public Object elConvertType(Object value) { - return value; - } + public CtCompoundPropertyElAdapter(CtCompoundProperty prop) { + this.prop = prop; + } - public Object elGetReference(EntityBean bean) { - return bean; - } + public void setDeployOrder(int deployOrder) { + this.deployOrder = deployOrder; + } - public Object elGetValue(EntityBean bean) { - return prop.getValue(bean); - } + public Object elConvertType(Object value) { + return value; + } - public void elSetValue(EntityBean bean, Object value, boolean populate) { - prop.setValue(bean, value); - } + public Object elGetReference(EntityBean bean) { + return bean; + } - public int getDeployOrder() { - return deployOrder; - } + public Object elGetValue(EntityBean bean) { + return prop.getValue(bean); + } - public String getAssocOneIdExpr(String prefix, String operator) { - throw new RuntimeException("Not Supported or Expected"); - } + @Override + public void set(Object bean, Object value) { + elSetValue((EntityBean) bean, value, true); + } - public Object[] getAssocOneIdValues(EntityBean bean) { - throw new RuntimeException("Not Supported or Expected"); - } - - public String getAssocIdInExpr(String prefix) { - throw new RuntimeException("Not Supported or Expected"); - } + public void elSetValue(EntityBean bean, Object value, boolean populate) { + prop.setValue(bean, value); + } - public String getAssocIdInValueExpr(int size) { - throw new RuntimeException("Not Supported or Expected"); - } + public int getDeployOrder() { + return deployOrder; + } - public BeanProperty getBeanProperty() { - return null; - } + public String getAssocOneIdExpr(String prefix, String operator) { + throw new RuntimeException("Not Supported or Expected"); + } - public StringParser getStringParser() { - return null; - } + public Object[] getAssocOneIdValues(EntityBean bean) { + throw new RuntimeException("Not Supported or Expected"); + } - public boolean isDbEncrypted() { - return false; - } + public String getAssocIdInExpr(String prefix) { + throw new RuntimeException("Not Supported or Expected"); + } - public boolean isLocalEncrypted() { - return false; - } + public String getAssocIdInValueExpr(int size) { + throw new RuntimeException("Not Supported or Expected"); + } - public boolean isAssocId() { - return false; - } - - public boolean isAssocProperty() { - return false; - } + public BeanProperty getBeanProperty() { + return null; + } - public boolean isDateTimeCapable() { - return false; - } + public StringParser getStringParser() { + return null; + } - public int getJdbcType() { - return 0; - } + public boolean isDbEncrypted() { + return false; + } - public Object parseDateTime(long systemTimeMillis) { - throw new RuntimeException("Not Supported or Expected"); - } - - @Override - public boolean containsFormulaWithJoin() { - return false; - } + public boolean isLocalEncrypted() { + return false; + } - public boolean containsMany() { - return false; - } + public boolean isAssocId() { + return false; + } - public boolean containsManySince(String sinceProperty) { - return containsMany(); - } - - public String getDbColumn() { - return null; - } + public boolean isAssocProperty() { + return false; + } - public String getElPlaceholder(boolean encrypted) { - return null; - } + public boolean isDateTimeCapable() { + return false; + } - public String getElPrefix() { - return null; - } + public int getJdbcType() { + return 0; + } - public String getName() { - return prop.getPropertyName(); - } + public Object parseDateTime(long systemTimeMillis) { + throw new RuntimeException("Not Supported or Expected"); + } - public String getElName() { - return prop.getPropertyName(); - } + @Override + public boolean containsFormulaWithJoin() { + return false; + } + + public boolean containsMany() { + return false; + } + + public boolean containsManySince(String sinceProperty) { + return containsMany(); + } + + public String getDbColumn() { + return null; + } + + public String getElPlaceholder(boolean encrypted) { + return null; + } + + public String getElPrefix() { + return null; + } + + public String getName() { + return prop.getPropertyName(); + } + + public String getElName() { + return prop.getPropertyName(); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index 8d3be52df..ebf05e87a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -560,7 +560,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { maxValueLen = Math.max(maxValueLen, value.length()); Object enumValue = Enum.valueOf(enumType, name.trim()); - beanDbMap.add(enumValue, value); + beanDbMap.add(enumValue, value, name.trim()); } if (dbColumnLength == 0 && !integerType) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java index d2c394015..583207b6a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java @@ -15,16 +15,16 @@ public class EnumToDbIntegerMap extends EnumToDbValueMap { return Types.INTEGER; } - public void add(Object beanValue, Integer dbValue) { - addInternal(beanValue, dbValue); + public void add(Object beanValue, Integer dbValue, String name) { + addInternal(beanValue, dbValue, name); } @Override - public EnumToDbIntegerMap add(Object beanValue, String stringDbValue) { + public EnumToDbIntegerMap add(Object beanValue, String stringDbValue, String name) { try { Integer value = Integer.valueOf(stringDbValue); - addInternal(beanValue, value); + addInternal(beanValue, value, name); return this; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java index d5f77818b..335b4197c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java @@ -15,8 +15,8 @@ public class EnumToDbStringMap extends EnumToDbValueMap { } @Override - public EnumToDbStringMap add(Object beanValue, String dbValue) { - addInternal(beanValue, dbValue); + public EnumToDbStringMap add(Object beanValue, String dbValue, String name) { + addInternal(beanValue, dbValue, name); return this; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java index dfe037a30..742aa864a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import java.sql.SQLException; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -21,6 +22,8 @@ public abstract class EnumToDbValueMap { final LinkedHashMap valueMap; + final HashMap nameMap; + final boolean allowNulls; final boolean isIntegerType; @@ -43,8 +46,9 @@ public abstract class EnumToDbValueMap { public EnumToDbValueMap(boolean allowNulls, boolean isIntegerType) { this.allowNulls = allowNulls; this.isIntegerType = isIntegerType; - keyMap = new LinkedHashMap(); - valueMap = new LinkedHashMap(); + this.keyMap = new LinkedHashMap(); + this.valueMap = new LinkedHashMap(); + this.nameMap = new HashMap(); } /** @@ -81,7 +85,7 @@ public abstract class EnumToDbValueMap { * Add name value pair where the dbValue is the raw string and may need to * be converted (to an Integer for example). */ - public abstract EnumToDbValueMap add(Object beanValue, String dbValue); + public abstract EnumToDbValueMap add(Object beanValue, String dbValue, String name); /** * Add a bean value and DB value pair. @@ -89,10 +93,11 @@ public abstract class EnumToDbValueMap { * The dbValue will be converted to an Integer if isIntegerType is true; *

*/ - protected void addInternal(Object beanValue, T dbValue) { + protected void addInternal(Object beanValue, T dbValue, String name) { keyMap.put(beanValue, dbValue); valueMap.put(dbValue, beanValue); + nameMap.put(name, beanValue); } /** @@ -118,6 +123,9 @@ public abstract class EnumToDbValueMap { return null; } Object beanValue = valueMap.get(dbValue); + if (beanValue == null) { + beanValue = nameMap.get(dbValue); + } if (beanValue == null && !allowNulls) { String msg = "Bean value for " + dbValue + " not found in " + valueMap; throw new IllegalArgumentException(msg); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java index c8982fa90..c462e7a03 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.text.StringFormatter; import com.avaje.ebean.text.StringParser; import com.avaje.ebean.text.json.JsonWriter; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -162,6 +163,11 @@ public interface ScalarType extends StringParser, StringFormatter, ScalarData */ T parse(String value); + /** + * Return the type this maps to for JSON document stores. + */ + DocPropertyType getDocType(); + /** * Return true if the type can accept long systemTimeMillis input. *

diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java index 739d9e435..37b31c45b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -82,6 +83,11 @@ public abstract class ScalarTypeBaseDate extends ScalarTypeBase { writer.writeNumberField(name, millis); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.DATE; + } + public T readData(DataInput dataInput) throws IOException { if (!dataInput.readBoolean()) { return null; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java index 934a0f323..c1c667699 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.config.JsonConfig; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -122,6 +123,11 @@ public abstract class ScalarTypeBaseDateTime extends ScalarTypeBase { } } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.DATETIME; + } + public String formatValue(T t) { Timestamp ts = convertToTimestamp(t); return ts.toString(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java index 5b0e932bd..2e592e6e0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; @@ -131,4 +132,10 @@ public abstract class ScalarTypeBaseVarchar extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, T value) throws IOException { writer.writeStringField(name, format(value)); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java index 8426802f2..7836a4592 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -87,4 +88,9 @@ public class ScalarTypeBigDecimal extends ScalarTypeBase { writer.writeNumberField(name, value); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.DOUBLE; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java index 154d4a331..e0eb96078 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -338,6 +339,11 @@ public class ScalarTypeBoolean { public void jsonWrite(JsonWriter writer, String name, Boolean value) throws IOException { writer.writeBooleanField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.BOOLEAN; + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java index 32792670d..0fb36b1ef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -51,6 +52,11 @@ public class ScalarTypeByte extends ScalarTypeBase { throw new IOException("Not supported"); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.BINARY; + } + public String formatValue(Byte t) { return t.toString(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java index 30a230be6..87dbb48e0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; @@ -52,6 +53,11 @@ public abstract class ScalarTypeBytesBase extends ScalarTypeBase { return out.toByteArray(); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.BINARY; + } + public String formatValue(byte[] t) { throw new TextException("Not supported"); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java index 412712398..378ce7cb1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.text.json.JsonWriter; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -75,6 +76,11 @@ public class ScalarTypeBytesEncrypted implements ScalarType { return out.toByteArray(); } + @Override + public DocPropertyType getDocType() { + return baseType.getDocType(); + } + public String format(Object v) { throw new RuntimeException("Not used"); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java index d1eb890c2..30f5e5a48 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java @@ -14,7 +14,7 @@ public class ScalarTypeDayOfWeek extends ScalarTypeEnumWithMapping { static { DayOfWeek[] values = DayOfWeek.values(); for (DayOfWeek value : values) { - beanDbMap.add(value, value.getValue()); + beanDbMap.add(value, value.getValue(), value.name()); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java index c7918a8d4..b3a7179d6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -93,4 +94,10 @@ public class ScalarTypeDouble extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Double value) throws IOException { writer.writeNumberField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.DOUBLE; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java index 2ed3169ce..f56df9506 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -110,4 +111,10 @@ public class ScalarTypeDuration extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Duration value) throws IOException { writer.writeStringField(name, value.toString()); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java index 171dc277d..ce571332c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.text.json.JsonWriter; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -141,4 +142,10 @@ public class ScalarTypeEncryptedWrapper implements ScalarType { public void jsonWrite(JsonWriter writer, String name, T value) throws IOException { wrapped.jsonWrite(writer, name, value); } + + @Override + public DocPropertyType getDocType() { + return wrapped.getDocType(); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java index 6f9004242..3491058f7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; @@ -241,6 +242,11 @@ public class ScalarTypeEnumStandard { writer.writeStringField(name, formatValue(value)); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.ENUM; + } + @Override public Object readData(DataInput dataInput) throws IOException { if (!dataInput.readBoolean()) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java index 4e6124169..dd21eaa7d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java @@ -1,5 +1,7 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; + import java.sql.SQLException; import java.util.Iterator; @@ -82,4 +84,9 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i return beanDbMap.getDbValue(beanValue); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java index be87a0542..fe5507eb4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; @@ -127,6 +128,11 @@ public class ScalarTypeFile extends ScalarTypeBase { return tempFile; } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.BINARY; + } + @Override public String formatValue(File file) { throw new TextException("Not supported"); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java index 2951bb638..51b55d111 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -93,4 +94,10 @@ public class ScalarTypeFloat extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Float value) throws IOException { writer.writeNumberField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.FLOAT; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java index 9312cc0ae..626f7c439 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -93,4 +94,10 @@ public class ScalarTypeInteger extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Integer value) throws IOException { writer.writeNumberField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.INTEGER; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java index a41d4d823..a82a3ebe7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -86,6 +87,11 @@ public class ScalarTypeJodaLocalTime extends ScalarTypeBase { } } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } + @Override public LocalTime convertFromMillis(long systemTimeMillis) { return new LocalTime(systemTimeMillis, DateTimeZone.getDefault()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java index 8b12a8df8..131a29a3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.EJson; import com.avaje.ebean.text.json.JsonWriter; @@ -205,6 +206,11 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.OBJECT; + } + @Override public Map jsonRead(JsonParser parser, JsonToken event) throws IOException { // at this point the BeanProperty has read the START_OBJECT token diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java index ebccef7ab..cf06749a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; @@ -215,6 +216,11 @@ public abstract class ScalarTypeJsonNode extends ScalarTypeBase { } } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.OBJECT; + } + @Override public JsonNode jsonRead(JsonParser parser, JsonToken event) throws IOException { // at this point the BeanProperty has read the START_OBJECT token diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java index d80af485d..8bbb56677 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -102,4 +103,10 @@ public class ScalarTypeLocalTime extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, LocalTime value) throws IOException { writer.writeStringField(name, value.toString()); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java index 8dffa3b56..1a464f1c4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -93,4 +94,10 @@ public class ScalarTypeLong extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Long value) throws IOException { writer.writeNumberField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.LONG; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java index 93bb233db..d6263fcae 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -101,4 +102,9 @@ public class ScalarTypeMathBigInteger extends ScalarTypeBase { writer.writeNumberField(name, value.longValue()); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.LONG; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java index faa863c5b..3c4384a2b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java @@ -14,7 +14,7 @@ public class ScalarTypeMonth extends ScalarTypeEnumWithMapping { static { Month[] values = Month.values(); for (Month value : values) { - beanDbMap.add(value, value.getValue()); + beanDbMap.add(value, value.getValue(), value.name()); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java index 9d491c091..d59474c36 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -128,5 +129,9 @@ public class ScalarTypeMonthDay extends ScalarTypeBase { writer.writeStringField(name, format(value)); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java index 23e807408..8da6f2ee5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.config.dbplatform.DbType; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.EJson; import com.avaje.ebean.text.json.JsonWriter; @@ -130,4 +131,8 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase { return EJson.parseObject(parser, event); } + @Override + public DocPropertyType getDocType() { + return DocPropertyType.OBJECT; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java index c6b4a2a97..b11c3525f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; @@ -94,4 +95,10 @@ public class ScalarTypeShort extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, Short value) throws IOException { writer.writeNumberField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.SHORT; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java index 03f786240..ff19ba3a0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -93,4 +94,9 @@ public class ScalarTypeString extends ScalarTypeBase { public void jsonWrite(JsonWriter writer, String name, String value) throws IOException { writer.writeStringField(name, value); } + + @Override + public DocPropertyType getDocType() { + return DocPropertyType.STRING; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java index 74ed0871f..6a8b8c97b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebean.text.json.JsonWriter; import com.avaje.ebeaninternal.server.core.BasicTypeConverter; import com.fasterxml.jackson.core.JsonParser; @@ -96,4 +97,9 @@ public class ScalarTypeTime extends ScalarTypeBase

+ * Some transactions (like bulk updates) might specifically turn off indexing for example. + */ + DocStoreEvent getEvent(PersistRequest.Type persistType, DocStoreEvent txnMode); + + /** + * Return the index type for this bean type. + */ + String getIndexType(); + + /** + * Return the index name for this bean type. + */ + String getIndexName(); + + /** + * Process a delete by id of a given document. + */ + void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException; + + /** + * Process an index event which is effectively an insert or update (or put). + */ + void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException; + + /** + * Process an insert persist request. + */ + void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException; + + /** + * Process an update persist request. + */ + void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException; + + /** + * Process the persist request adding any embedded/nested document invalidation to the docStoreUpdates. + *

+ * This is expected to check the specific properties to see what other documents they are nested in + * and register invalidation events based on that. + * + * @param request The persist request + * @param docStoreUpdates Invalidation events are registered to this docStoreUpdates + */ + void updateEmbedded(PersistRequestBean request, DocStoreUpdates docStoreUpdates); + + /** + * Process an update of an embedded document. + * + * @param idValue the id of the bean effected by an embedded document update + * @param embeddedProperty the path of the property + * @param embeddedRawContent the embedded content for this property in JSON form + * @param txn the doc store transaction to use to process the update + */ + void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException; + + /** + * Create the document mapping. + */ + DocumentMapping createDocMapping(); + + /** + * Return an un-analysed property to use instead of the given property. + *

+ * For analysed properties that we want to sort on we will map the property to an additional + * 'raw' property that we can use for sorting etc. + *

+ */ + String rawProperty(String property); +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java new file mode 100644 index 000000000..1579e2e70 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java @@ -0,0 +1,22 @@ +package com.avaje.ebeanservice.docstore.api; + +import com.avaje.ebean.plugin.SpiServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Creates the integration components for DocStore integration. + */ +public interface DocStoreFactory { + + /** + * Create and return the DocStore integration components. + */ + DocStoreIntegration create(SpiServer server); + + /** + * Create the doc store specific adapter for the given bean type. + */ + DocStoreBeanAdapter createAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy); + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java new file mode 100644 index 000000000..5a6911788 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java @@ -0,0 +1,20 @@ +package com.avaje.ebeanservice.docstore.api; + +import com.avaje.ebean.DocumentStore; + +/** + * All the required features for DocStore integration. + */ +public interface DocStoreIntegration { + + /** + * Return the DocStoreUpdateProcessor to use. + */ + DocStoreUpdateProcessor updateProcessor(); + + /** + * Return the DocStore. + */ + DocumentStore documentStore(); + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java new file mode 100644 index 000000000..2a0ff8b41 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java @@ -0,0 +1,23 @@ +package com.avaje.ebeanservice.docstore.api; + +import java.io.IOException; + +/** + * Update the document store using a Ebean ORM query. + *

+ * Executes a forEach query and updates the document store with the bean object graph returned by the query. + *

+ */ +public interface DocStoreQueryUpdate { + + /** + * Process the bean storing in the document store. + */ + void store(Object idValue, T bean) throws IOException; + + /** + * Flush the changes to the document store. + */ + void flush() throws IOException; + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java new file mode 100644 index 000000000..d07774033 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java @@ -0,0 +1,19 @@ +package com.avaje.ebeanservice.docstore.api; + +import java.io.IOException; + +/** + * For persist events that know how to publish or queue their change to the Document store. + */ +public interface DocStoreUpdate { + + /** + * Add the event to the doc store bulk update. + */ + void docStoreUpdate(DocStoreUpdateContext txn) throws IOException; + + /** + * Add to the queue for deferred processing. + */ + void addToQueue(DocStoreUpdates docStoreUpdates); +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java new file mode 100644 index 000000000..b6494710b --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java @@ -0,0 +1,11 @@ +package com.avaje.ebeanservice.docstore.api; + +/** + * The doc store specific context/transaction used to collect updates to send to the document store. + *

+ * Doc store specific implementations gather changes and bulk update the document store. + *

+ */ +public interface DocStoreUpdateContext { + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java new file mode 100644 index 000000000..abf491bc2 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java @@ -0,0 +1,34 @@ +package com.avaje.ebeanservice.docstore.api; + +import com.avaje.ebean.plugin.BeanType; + +import java.io.IOException; + +/** + * Processes index updates. + *

+ * This involves sending updates directly to ElasticSearch via it's Bulk API or + * queuing events for future processing. + *

+ */ +public interface DocStoreUpdateProcessor { + + /** + * Create a processor to handle updates per bean via a findEach query. + */ + DocStoreQueryUpdate createQueryUpdate(BeanType beanType, int bulkBatchSize) throws IOException; + + /** + * Process all the updates for a transaction. + *

+ * Typically this makes calls to the Bulk API of the document store or simply adds entries + * to a queue for future processing. + *

+ * + * @param docStoreUpdates The 'Bulk' and 'Queue' updates to the indexes for the transaction. + * @param bulkBatchSize The batch size to use for Bulk API calls specified on the transaction. + * If this is 0 then the default batch size is used. + */ + void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize); + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java new file mode 100644 index 000000000..cf64d524e --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java @@ -0,0 +1,115 @@ +package com.avaje.ebeanservice.docstore.api; + +import com.avaje.ebean.DocStoreQueueEntry; +import com.avaje.ebean.DocStoreQueueEntry.Action; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collection of document store updates that are either sent to the document store + * or queued for future processing + */ +public class DocStoreUpdates { + + /** + * Persist inserts and updates. + */ + private final List persistEvents = new ArrayList(); + + /** + * Delete by Id. + */ + private final List deleteEvents = new ArrayList(); + + /** + * Nested updates. + */ + private final List nestedEvents = new ArrayList(); + + /** + * Entries sent to the queue for later processing. + */ + private final List queueEntries = new ArrayList(); + + public DocStoreUpdates() { + } + + /** + * Return true if there are no events to process. + */ + public boolean isEmpty() { + return persistEvents.isEmpty() && deleteEvents.isEmpty() && nestedEvents.isEmpty() && queueEntries.isEmpty(); + } + + /** + * Add a persist request. + */ + public void addPersist(DocStoreUpdate bulkRequest) { + persistEvents.add(bulkRequest); + } + + /** + * Add a delete request. + */ + public void addDelete(DocStoreUpdate bulkRequest) { + deleteEvents.add(bulkRequest); + } + + /** + * Add a nested update. + */ + public void addNested(String queueId, String path, Object beanId) { + nestedEvents.add(new DocStoreQueueEntry(Action.NESTED, queueId, path, beanId)); + } + + /** + * Queue an 'index' request. + */ + public void queueIndex(String queueId, Object beanId) { + queueEntries.add(new DocStoreQueueEntry(Action.INDEX, queueId, beanId)); + } + + /** + * Queue a 'delete' request. + */ + public void queueDelete(String queueId, Object beanId) { + queueEntries.add(new DocStoreQueueEntry(Action.DELETE, queueId, beanId)); + } + + /** + * Queue an update to a nested/embedded object. + */ + public void queueNested(String queueId, String path, Object beanId) { + queueEntries.add(new DocStoreQueueEntry(Action.NESTED, queueId, path, beanId)); + } + + /** + * Return the persist insert and update requests to be sent to the document store. + */ + public List getPersistEvents() { + return persistEvents; + } + + /** + * Return delete events. + */ + public List getDeleteEvents() { + return deleteEvents; + } + + /** + * Return the list of nested update events. + */ + public List getNestedEvents() { + return nestedEvents; + } + + /** + * Return the entries for sending to the queue. + */ + public List getQueueEntries() { + return queueEntries; + } + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java new file mode 100644 index 000000000..59f51e546 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java @@ -0,0 +1,15 @@ +package com.avaje.ebeanservice.docstore.api; + +/** + * Can be thrown when a document is unexpectedly not found in a document store. + */ +public class DocumentNotFoundException extends RuntimeException { + + /** + * Construct with a message. + */ + public DocumentNotFoundException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java new file mode 100644 index 000000000..9f3ab7d23 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java @@ -0,0 +1,143 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +import com.avaje.ebean.annotation.DocMapping; +import com.avaje.ebean.annotation.DocStore; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.server.query.SplitName; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Stack; + +/** + * Builds the DocumentMapping for a given bean type. + */ +public class DocMappingBuilder { + + private final PathProperties paths; + + private final DocStore docStore; + + private final Stack properties = new Stack(); + + private final Map map = new LinkedHashMap(); + + /** + * Create with the document structure paths and docStore deployment annotation. + */ + public DocMappingBuilder(PathProperties paths, DocStore docStore) { + this.paths = paths; + this.docStore = docStore; + this.properties.push(new DocPropertyMapping()); + } + + /** + * Return true if the property is included in the document. + */ + public boolean includesProperty(String prefix, String name) { + return paths.includesProperty(prefix, name); + } + + /** + * Return true if the path is included in the document. + */ + public boolean includesPath(String prefix, String name) { + return paths.includesProperty(prefix, name); + } + + /** + * Add the property mapping. + */ + public void add(DocPropertyMapping docMapping) { + + DocPropertyMapping currentParent = properties.peek(); + currentParent.addChild(docMapping); + + String parentName = currentParent.getName(); + String fullName = SplitName.add(parentName, docMapping.getName()); + map.put(fullName, docMapping); + } + + /** + * Push the nested object or list onto the properties stack. + */ + public void push(DocPropertyMapping nested) { + properties.push(nested); + } + + /** + * Pop the nested object or list off the properties stack. + */ + public void pop() { + properties.pop(); + } + + /** + * Apply any override mappings from the top level docStore annotation. + */ + public void applyMapping() { + + DocMapping[] mapping = docStore.mapping(); + for (DocMapping docMapping : mapping) { + applyFieldMapping(null, docMapping); + } + } + + private void applyFieldMapping(String prefix, DocMapping docMapping) { + + String name = docMapping.name(); + String fullName = SplitName.add(prefix, name); + + DocPropertyMapping mapping = map.get(fullName); + if (mapping == null) { + throw new IllegalStateException("DocMapping for ["+fullName+"] but property not included in document?"); + } + mapping.apply(docMapping); + } + + /** + * Collect the mapping of properties to 'raw' properties for those marked as sortable. + */ + public Map collectSortable() { + + DocPropertyMapping peek = properties.peek(); + SortableVisitor visitor = new SortableVisitor(); + peek.visit(visitor); + + return visitor.getSortableMap(); + } + + /** + * Create the document mapping. + */ + public DocumentMapping create(String queueId, String indexName, String indexType) { + + int shards = docStore.shards(); + int replicas = docStore.replicas(); + DocPropertyMapping root = properties.peek(); + return new DocumentMapping(queueId, indexName, indexType, paths, root, shards, replicas); + } + + + /** + * Find sortable properties to build the mapping to 'raw' properties. + */ + private static class SortableVisitor extends DocPropertyAdapter { + + private Map sortableMap = new LinkedHashMap(); + + @Override + public void visitProperty(DocPropertyMapping property) { + + DocPropertyOptions options = property.getOptions(); + if (options != null && Boolean.TRUE.equals(options.getSortable())) { + String fullPath = pathStack.peekFullPath(property.getName()); + sortableMap.put(fullPath, fullPath+".raw"); + } + } + + private Map getSortableMap() { + return sortableMap; + } + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java new file mode 100644 index 000000000..b7942253a --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java @@ -0,0 +1,47 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +import com.avaje.ebeaninternal.server.text.json.PathStack; + +/** + * Adapter for DocPropertyVisitor that does not do anything. + * Used to extend and implement only the desired methods. + */ +public abstract class DocPropertyAdapter implements DocPropertyVisitor { + + protected PathStack pathStack = new PathStack(); + + @Override + public void visitProperty(DocPropertyMapping property) { + // do nothing + } + + @Override + public void visitBegin() { + // do nothing + } + + @Override + public void visitEnd() { + // do nothing + } + + @Override + public void visitBeginObject(DocPropertyMapping property) { + pathStack.push(property.getName()); + } + + @Override + public void visitEndObject(DocPropertyMapping property) { + pathStack.pop(); + } + + @Override + public void visitBeginList(DocPropertyMapping property) { + pathStack.push(property.getName()); + } + + @Override + public void visitEndList(DocPropertyMapping property) { + pathStack.pop(); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java new file mode 100644 index 000000000..24681d85e --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java @@ -0,0 +1,130 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +import com.avaje.ebean.annotation.DocMapping; + +import java.util.ArrayList; +import java.util.List; + +/** + * Property mapping in a doc store document structure. + */ +public class DocPropertyMapping { + + private String name; + + private DocPropertyType type; + + private DocPropertyOptions options; + + private List children = new ArrayList(); + + /** + * Construct ROOT. + */ + public DocPropertyMapping() { + this.type = DocPropertyType.ROOT; + } + + /** + * Construct property mapping. + */ + public DocPropertyMapping(String name, DocPropertyType type) { + this.type = type; + this.name = name; + this.options = new DocPropertyOptions(); + } + + /** + * Construct property mapping with options. + */ + public DocPropertyMapping(String name, DocPropertyType type, DocPropertyOptions options) { + this.name = name; + this.type = type; + this.options = options; + } + + /** + * Visit this property and any nested children. + */ + public void visit(DocPropertyVisitor visitor) { + switch (type) { + case ROOT: + visitor.visitBegin(); + visitChildren(visitor); + visitor.visitEnd(); + break; + case OBJECT: + visitor.visitBeginObject(this); + visitChildren(visitor); + visitor.visitEndObject(this); + break; + case LIST: + visitor.visitBeginList(this); + visitChildren(visitor); + visitor.visitEndList(this); + break; + default: + visitor.visitProperty(this); + } + } + + private void visitChildren(DocPropertyVisitor visitor) { + + for (DocPropertyMapping property : children) { + property.visit(visitor); + } + } + + public String toString() { + return "name:"+name+" type:"+type+" options("+options+")"; + } + + /** + * Return the type of the property. + */ + public DocPropertyType getType() { + return type; + } + + /** + * Set the type of the property. + */ + public void setType(DocPropertyType type) { + this.type = type; + } + + /** + * Return the property name. + */ + public String getName() { + return name; + } + + /** + * Return the property options. + */ + public DocPropertyOptions getOptions() { + return options; + } + + /** + * Return the child nested properties. + */ + public List getChildren() { + return children; + } + + /** + * Add a child property. + */ + public void addChild(DocPropertyMapping docMapping) { + children.add(docMapping); + } + + /** + * Apply mapping options to this property. + */ + public void apply(DocMapping docMapping) { + options.apply(docMapping); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java new file mode 100644 index 000000000..2602c8cd7 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java @@ -0,0 +1,138 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +import com.avaje.ebean.annotation.DocMapping; + +/** + * Options for mapping a property for document storage. + */ +public class DocPropertyOptions { + + private Boolean code; + + private Boolean sortable; + + private Boolean store; + + private Float boost; + + private String nullValue; + + /** + * Construct with no values set. + */ + public DocPropertyOptions() { + + } + + /** + * Construct as a copy of the source options. + */ + protected DocPropertyOptions(DocPropertyOptions source) { + this.code = source.code; + this.sortable = source.sortable; + this.store = source.store; + this.boost = source.boost; + this.nullValue = source.nullValue; + } + + /** + * Construct with options set. + */ + public DocPropertyOptions(Boolean code, Boolean sortable, Boolean store, Float boost, String nullValue) { + this.code = code; + this.sortable = sortable; + this.store = store; + this.boost = boost; + this.nullValue = nullValue; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + if (code != null) { + sb.append("code:").append(code).append(" "); + } + if (sortable != null) { + sb.append("sortable:").append(sortable).append(" "); + } + if (store != null) { + sb.append("store:").append(store).append(" "); + } + if (boost != null) { + sb.append("boost:").append(boost).append(" "); + } + if (nullValue != null) { + sb.append("nullValue:").append(nullValue).append(" "); + } + return sb.toString(); + } + + public Boolean getCode() { + return code; + } + + public void setCode(Boolean code) { + this.code = code; + } + + public Boolean getSortable() { + return sortable; + } + + public void setSortable(Boolean sortable) { + this.sortable = sortable; + } + + public Float getBoost() { + return boost; + } + + public void setBoost(Float boost) { + this.boost = boost; + } + + public String getNullValue() { + return nullValue; + } + + public void setNullValue(String nullValue) { + this.nullValue = nullValue; + } + + public Boolean getStore() { + return store; + } + + public void setStore(Boolean store) { + this.store = store; + } + + /** + * Create a copy of this such that it can be overridden on a per index basis. + */ + public DocPropertyOptions copy() { + return new DocPropertyOptions(this); + } + + /** + * Apply override mapping from the document level or embedded property level. + */ + public void apply(DocMapping docMapping) { + + if (docMapping.code()) { + code = true; + } + if (docMapping.sortable()) { + sortable = true; + } + if (docMapping.store()) { + store = true; + } + if (docMapping.boost() != 1) { + boost = docMapping.boost(); + } + if (!"".equals(docMapping.nullValue())) { + nullValue = docMapping.nullValue(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java new file mode 100644 index 000000000..48e826db2 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java @@ -0,0 +1,78 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +/** + * Types as defined for document store property types. + */ +public enum DocPropertyType { + + /** + * Enum. + */ + ENUM, + + /** + * String. + */ + STRING, + + /** + * Boolean. + */ + BOOLEAN, + + /** + * Short. + */ + SHORT, + + /** + * Integer. + */ + INTEGER, + + /** + * Long. + */ + LONG, + + /** + * Float. + */ + FLOAT, + + /** + * Double. + */ + DOUBLE, + + /** + * Date without time. + */ + DATE, + + /** + * Date with time. + */ + DATETIME, + + /** + * Binary type. + */ + BINARY, + + /** + * A nested object. + */ + OBJECT, + + /** + * A nested list of objects. + */ + LIST, + + /** + * Root level type. + */ + ROOT + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java new file mode 100644 index 000000000..6ce9540a7 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java @@ -0,0 +1,43 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +/** + * Used to visit the properties in a document structure. + */ +public interface DocPropertyVisitor { + + /** + * Begin visiting the document structure. + */ + void visitBegin(); + + /** + * Visit a property. + */ + void visitProperty(DocPropertyMapping property); + + /** + * Start visiting a nested object. + */ + void visitBeginObject(DocPropertyMapping property); + + /** + * End visiting a nested object. + */ + void visitEndObject(DocPropertyMapping property); + + /** + * Start visiting a nested list. + */ + void visitBeginList(DocPropertyMapping property); + + /** + * End visiting a nested list. + */ + void visitEndList(DocPropertyMapping property); + + /** + * Finished visiting the document structure. + */ + void visitEnd(); + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java new file mode 100644 index 000000000..15d32cc72 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java @@ -0,0 +1,103 @@ +package com.avaje.ebeanservice.docstore.api.mapping; + +import com.avaje.ebean.FetchPath; + +/** + * Mapping for a document stored in a doc store (like ElasticSearch). + */ +public class DocumentMapping { + + protected final String queueId; + + protected final String name; + + protected final String type; + + protected final FetchPath paths; + + protected final DocPropertyMapping properties; + + protected int shards; + + protected int replicas; + + public DocumentMapping(String queueId, String name, String type, FetchPath paths, DocPropertyMapping properties, int shards, int replicas) { + this.queueId = queueId; + this.name = name; + this.type = type; + this.paths = paths; + this.properties = properties; + this.shards = shards; + this.replicas = replicas; + } + + /** + * Visit all the properties in the document structure. + */ + public void visit(DocPropertyVisitor visitor) { + properties.visit(visitor); + } + + /** + * Return the queueId. + */ + public String getQueueId() { + return queueId; + } + + /** + * Return the name. + */ + public String getName() { + return name; + } + + /** + * Return the type. + */ + public String getType() { + return type; + } + + /** + * Return the document structure as PathProperties. + */ + public FetchPath getPaths() { + return paths; + } + + /** + * Return the document structure with mapping details. + */ + public DocPropertyMapping getProperties() { + return properties; + } + + /** + * Return the number of shards. + */ + public int getShards() { + return shards; + } + + /** + * Set the number of shards. + */ + public void setShards(int shards) { + this.shards = shards; + } + + /** + * Return the number of replicas. + */ + public int getReplicas() { + return replicas; + } + + /** + * Set the number of replicas. + */ + public void setReplicas(int replicas) { + this.replicas = replicas; + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java new file mode 100644 index 000000000..076a3fc98 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java @@ -0,0 +1,315 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.Query; +import com.avaje.ebean.annotation.DocStore; +import com.avaje.ebean.annotation.DocStoreEvent; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; +import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder; +import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Base implementation for much of DocStoreBeanAdapter. + */ +public abstract class DocStoreBeanBaseAdapter implements DocStoreBeanAdapter { + + protected final SpiEbeanServer server; + + /** + * The associated BeanDescriptor. + */ + protected final BeanDescriptor desc; + + /** + * The type of index. + */ + protected final boolean mapped; + + /** + * Nested path properties defining the doc structure for indexing. + */ + protected final DocStructure docStructure; + + /** + * Identifier used in the queue system to identify the index. + */ + protected final String queueId; + + /** + * ElasticSearch index type. + */ + protected final String indexType; + + /** + * ElasticSearch index name. + */ + protected final String indexName; + + /** + * Doc store deployment annotation. + */ + private final DocStore docStore; + + /** + * Behavior on insert. + */ + protected final DocStoreEvent insert; + + /** + * Behavior on update. + */ + protected final DocStoreEvent update; + + /** + * Behavior on delete. + */ + protected final DocStoreEvent delete; + + /** + * List of embedded paths from other documents that include this document type. + * As such an update to this doc type means that those embedded documents need to be updated. + */ + protected final List embeddedInvalidation = new ArrayList(); + + /** + * Map of properties to 'raw' properties. + */ + private Map sortableMap; + + + public DocStoreBeanBaseAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) { + + this.desc = desc; + this.server = desc.getEbeanServer(); + this.mapped = deploy.isDocStoreMapped(); + this.docStructure = (!mapped) ? null : derivePathProperties(deploy); + this.docStore = deploy.getDocStore(); + this.queueId = derive(desc, deploy.getDocStoreQueueId()); + this.indexName = derive(desc, deploy.getDocStoreIndexName()); + this.indexType = derive(desc, deploy.getDocStoreIndexType()); + this.insert = deploy.getDocStoreInsertEvent(); + this.update = deploy.getDocStoreUpdateEvent(); + this.delete = deploy.getDocStoreDeleteEvent(); + } + + @Override + public DocumentMapping createDocMapping() { + + if (!mapped) return null; + + PathProperties paths = docStructure.doc(); + + DocMappingBuilder mappingBuilder = new DocMappingBuilder(paths, docStore); + desc.docStoreMapping(mappingBuilder, null); + + mappingBuilder.applyMapping(); + prepareMapping(mappingBuilder); + + sortableMap = mappingBuilder.collectSortable(); + + docStructure.prepareMany(desc); + + return mappingBuilder.create(queueId, indexName, indexType); + } + + protected void prepareMapping(DocMappingBuilder mappingBuilder) { + // do nothing by default + } + + @Override + public String getIndexType() { + return indexType; + } + + @Override + public String getIndexName() { + return indexName; + } + + @Override + public void applyPath(Query query) { + query.apply(docStructure.doc()); + } + + @Override + public String rawProperty(String property) { + + String rawProperty = sortableMap.get(property); + return rawProperty == null ? property : rawProperty; + } + + /** + * Register invalidation paths for embedded documents. + */ + @Override + public void registerPaths() { + if (mapped) { + Collection pathProps = docStructure.doc().getPathProps(); + for (PathProperties.Props pathProp : pathProps) { + String path = pathProp.getPath(); + if (path != null) { + BeanDescriptor targetDesc = desc.getBeanDescriptor(path); + String idName = targetDesc.getIdProperty().getName(); + String fullPath = path + "." + idName; + targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties()); + } + } + } + } + + /** + * Register a doc store invalidation listener for the given bean type, path and properties. + */ + @Override + public void registerInvalidationPath(String queueId, String path, Set properties) { + + embeddedInvalidation.add(getEmbeddedInvalidation(queueId, path, properties)); + } + + /** + * Return the DsInvalidationListener based on the properties, path. + */ + protected DocStoreEmbeddedInvalidation getEmbeddedInvalidation(String queueId, String path, Set properties) { + + if (properties.contains("*")) { + return new DocStoreEmbeddedInvalidation(queueId, path); + } else { + return new DocStoreEmbeddedInvalidationProperties(queueId, path, getPropertyPositions(properties)); + } + } + + /** + * Return the property names as property index positions. + */ + protected int[] getPropertyPositions(Set properties) { + List posList = new ArrayList(); + for (String property : properties) { + BeanProperty prop = desc.getBeanProperty(property); + if (prop != null) { + posList.add(prop.getPropertyIndex()); + } + } + int[] pos = new int[posList.size()]; + for (int i = 0; i request, DocStoreUpdates docStoreUpdates) { + for (int i = 0; i < embeddedInvalidation.size(); i++) { + embeddedInvalidation.get(i).embeddedInvalidate(request, docStoreUpdates); + } + } + + /** + * Return the pathProperties which defines the JSON document to index. + * This can add derived/embedded/nested parts to the document. + */ + protected DocStructure derivePathProperties(DeployBeanDescriptor deploy) { + + if (!mapped) { + return null; + } + + PathProperties pathProps = deploy.getDocStorePathProperties(); + boolean includeByDefault = (pathProps == null); + if (pathProps == null) { + pathProps = new PathProperties(); + } + + return getDocStructure(pathProps, includeByDefault); + } + + protected DocStructure getDocStructure(PathProperties pathProps, boolean includeByDefault) { + + DocStructure docStructure = new DocStructure(pathProps); + BeanProperty[] properties = desc.propertiesNonTransient(); + for (int i = 0; i < properties.length; i++) { + properties[i].docStoreInclude(includeByDefault, docStructure); + } + return docStructure; + } + + public FetchPath getEmbedded(String path) { + return docStructure.getEmbedded(path); + } + + public FetchPath getEmbeddedManyRoot(String path) { + return docStructure.getEmbeddedManyRoot(path); + } + + @Override + public boolean isMapped() { + return mapped; + } + + @Override + public String getQueueId() { + return queueId; + } + + @Override + public DocStoreEvent getEvent(PersistRequest.Type persistType, DocStoreEvent txnMode) { + + if (txnMode == null) { + return getDocStoreEvent(persistType); + } else if (txnMode == DocStoreEvent.IGNORE) { + return DocStoreEvent.IGNORE; + } + return mapped ? txnMode : getDocStoreEvent(persistType); + } + + private DocStoreEvent getDocStoreEvent(PersistRequest.Type persistType) { + switch (persistType) { + case INSERT: + return insert; + case UPDATE: + return update; + case DELETE: + return delete; + default: + return DocStoreEvent.IGNORE; + } + } + + /** + * Return the supplied value or default to the bean name lower case. + */ + protected String derive(BeanDescriptor desc, String suppliedValue) { + return (suppliedValue != null && suppliedValue.length() > 0) ? suppliedValue : desc.getName().toLowerCase(); + } + + @Override + public abstract void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException; + + @Override + public abstract void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException; + + @Override + public abstract void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException; + + @Override + public abstract void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException; + + @Override + public abstract void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException; + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java new file mode 100644 index 000000000..3bb51b6c9 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java @@ -0,0 +1,39 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdate; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + +import java.io.IOException; + +/** + * A 'Delete by Id' request that is send to the document store. + */ +public class DocStoreDeleteEvent implements DocStoreUpdate { + + private final BeanType beanType; + + private final Object idValue; + + public DocStoreDeleteEvent(BeanType beanType, Object idValue) { + this.beanType = beanType; + this.idValue = idValue; + } + + /** + * Add appropriate JSON content for sending to the ElasticSearch Bulk API. + */ + @Override + public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException { + beanType.docStore().deleteById(idValue, txn); + } + + /** + * Add this event to the queue (for queue delayed processing). + */ + @Override + public void addToQueue(DocStoreUpdates docStoreUpdates) { + docStoreUpdates.queueDelete(beanType.getDocStoreQueueId(), idValue); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java new file mode 100644 index 000000000..fdab9dc0b --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java @@ -0,0 +1,23 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + +/** + * Checks if a persist request means an embedded/nested object in another document needs updating. + */ +public class DocStoreEmbeddedInvalidation { + + protected final String queueId; + + protected final String path; + + public DocStoreEmbeddedInvalidation(String queueId, String path) { + this.queueId = queueId; + this.path = path; + } + + public void embeddedInvalidate(PersistRequestBean request, DocStoreUpdates docStoreUpdates) { + docStoreUpdates.addNested(queueId, path, request.getBeanId()); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java new file mode 100644 index 000000000..c133d73a6 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java @@ -0,0 +1,30 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + +/** + * Checks if a persist request means an embedded/nested object in another document needs updating. + * + * This has specific properties to check (so not all properties invalidate). + */ +public final class DocStoreEmbeddedInvalidationProperties extends DocStoreEmbeddedInvalidation { + + /** + * Properties that trigger invalidation. + */ + private final int[] properties; + + public DocStoreEmbeddedInvalidationProperties(String queueId, String path, int[] properties) { + super(queueId, path); + this.properties = properties; + } + + @Override + public void embeddedInvalidate(PersistRequestBean request, DocStoreUpdates docStoreUpdates) { + if (request.hasDirtyProperty(properties)) { + docStoreUpdates.addNested(queueId, path, request.getBeanId()); + } + } + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java new file mode 100644 index 000000000..763fbf8f2 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java @@ -0,0 +1,42 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdate; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + +import java.io.IOException; + +/** + * A 'Delete by Id' request that is send to the document store. + */ +public class DocStoreIndexEvent implements DocStoreUpdate { + + private final BeanType beanType; + + private final Object idValue; + + private final T bean; + + public DocStoreIndexEvent(BeanType beanType, Object idValue, T bean) { + this.beanType = beanType; + this.idValue = idValue; + this.bean = bean; + } + + /** + * Add appropriate JSON content for sending to the ElasticSearch Bulk API. + */ + @Override + public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException { + beanType.docStore().index(idValue, bean, txn); + } + + /** + * Add this event to the queue (for queue delayed processing). + */ + @Override + public void addToQueue(DocStoreUpdates docStoreUpdates) { + docStoreUpdates.queueIndex(beanType.getDocStoreQueueId(), idValue); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java new file mode 100644 index 000000000..87a07a01b --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java @@ -0,0 +1,96 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.FetchPath; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Document structure for mapping to document store. + */ +public class DocStructure { + + /** + * The full document structure. + */ + private final PathProperties doc; + + /** + * The embedded document structures by path. + */ + private final Map embedded = new HashMap(); + + private final Map manyRoot = new HashMap(); + + /** + * Create given an initial deployment doc mapping. + */ + public DocStructure(PathProperties pathProps) { + this.doc = pathProps; + } + + /** + * Add a property at the root level. + */ + public void addProperty(String name) { + doc.addToPath(null, name); + } + + /** + * Add an embedded property with it's document structure. + */ + public void addNested(String path, PathProperties embeddedDoc) { + doc.addNested(path, embeddedDoc); + embedded.put(path, embeddedDoc); + } + + /** + * Return the document structure. + */ + public PathProperties doc() { + return doc; + } + + /** + * Return the document structure for an embedded path. + */ + public FetchPath getEmbedded(String path) { + return embedded.get(path); + } + + public FetchPath getEmbeddedManyRoot(String path) { + return manyRoot.get(path); + } + + /** + * For 'many' nested properties we need an additional root based graph to fetch and update. + */ + public void prepareMany(BeanDescriptor desc) { + Set strings = embedded.keySet(); + for (String prop : strings) { + BeanPropertyAssoc embProp = (BeanPropertyAssoc)desc.getBeanProperty(prop); + if (embProp.isMany()) { + prepare(prop, embProp); + } + } + } + + /** + * Add a PathProperties for an embedded 'many' property (at the root level). + */ + private void prepare(String prop, BeanPropertyAssoc embProp) { + + BeanDescriptor targetDesc = embProp.getTargetDescriptor(); + + PathProperties manyRootPath = new PathProperties(); + manyRootPath.addToPath(null, targetDesc.getIdProperty().getName()); + manyRootPath.addNested(prop, embedded.get(prop)); + + manyRoot.put(prop, manyRootPath); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java new file mode 100644 index 000000000..e9b3d9285 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java @@ -0,0 +1,82 @@ +package com.avaje.ebeanservice.docstore.none; + +import com.avaje.ebean.DocStoreQueueEntry; +import com.avaje.ebean.DocumentStore; +import com.avaje.ebean.PagedList; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryEachConsumer; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +/** + * DocumentStore that barfs it is used. + */ +public class NoneDocStore implements DocumentStore { + + public static IllegalStateException implementationNotInClassPath() { + throw new IllegalStateException("DocStore implementation not included in the classPath. You need to add the maven dependency for avaje-ebeanorm-elastic"); + } + + @Override + public void dropIndex(String newIndex) { + throw implementationNotInClassPath(); + } + + @Override + public void createIndex(String indexName, String alias, String mappingResource) { + throw implementationNotInClassPath(); + } + + @Override + public void indexAll(Class countryClass) { + throw implementationNotInClassPath(); + } + + @Override + public long copyIndex(Class beanType, String newIndex) { + throw implementationNotInClassPath(); + } + + @Override + public long copyIndex(Class beanType, String newIndex, long epochMillis) { + throw implementationNotInClassPath(); + } + + @Override + public void indexByQuery(Query query) { + throw implementationNotInClassPath(); + } + + @Override + public void indexByQuery(Query query, int bulkBatchSize) { + throw implementationNotInClassPath(); + } + + @Nullable + @Override + public T getById(Class beanType, Object id) { + throw implementationNotInClassPath(); + } + + @Override + public PagedList findPagedList(Query query) { + throw implementationNotInClassPath(); + } + + @Override + public List findList(Query query) { + throw implementationNotInClassPath(); + } + + @Override + public void findEach(Query query, QueryEachConsumer consumer) { + throw implementationNotInClassPath(); + } + + @Override + public long process(List queueEntries) throws IOException { + throw implementationNotInClassPath(); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java new file mode 100644 index 000000000..b53816ba3 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java @@ -0,0 +1,49 @@ +package com.avaje.ebeanservice.docstore.none; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.ebeanservice.docstore.api.support.DocStoreBeanBaseAdapter; + +import java.io.IOException; + +/** + * DocStoreBeanBaseAdapter that barfs if it is used. + */ +public class NoneDocStoreBeanAdapter extends DocStoreBeanBaseAdapter { + + public NoneDocStoreBeanAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) { + super(desc, deploy); + } + + @Override + public boolean isMapped() { + return false; + } + + @Override + public void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java new file mode 100644 index 000000000..41547ff11 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java @@ -0,0 +1,39 @@ +package com.avaje.ebeanservice.docstore.none; + +import com.avaje.ebean.DocumentStore; +import com.avaje.ebean.plugin.SpiServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreFactory; +import com.avaje.ebeanservice.docstore.api.DocStoreIntegration; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; + +/** + * A stub implementation of DocStoreFactory that will barf if the docStore features are used. + */ +public class NoneDocStoreFactory implements DocStoreFactory { + + @Override + public DocStoreIntegration create(SpiServer server) { + return new NoneIntegration(); + } + + @Override + public DocStoreBeanAdapter createAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) { + return new NoneDocStoreBeanAdapter(desc, deploy); + } + + static class NoneIntegration implements DocStoreIntegration { + + @Override + public DocStoreUpdateProcessor updateProcessor() { + return new NoneDocStoreUpdateProcessor(); + } + + @Override + public DocumentStore documentStore() { + return new NoneDocStore(); + } + } +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java new file mode 100644 index 000000000..f32486697 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java @@ -0,0 +1,24 @@ +package com.avaje.ebeanservice.docstore.none; + +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebeanservice.docstore.api.DocStoreQueryUpdate; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; + +import java.io.IOException; + +/** + * DocStoreUpdateProcessor that barfs if it is used. + */ +public class NoneDocStoreUpdateProcessor implements DocStoreUpdateProcessor { + + @Override + public DocStoreQueryUpdate createQueryUpdate(BeanType beanType, int bulkBatchSize) throws IOException { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize) { + throw NoneDocStore.implementationNotInClassPath(); + } +} diff --git a/src/test/java/com/avaje/ebean/TestFilterWithEnum.java b/src/test/java/com/avaje/ebean/TestFilterWithEnum.java index 1c6d72f2c..252cfec2b 100644 --- a/src/test/java/com/avaje/ebean/TestFilterWithEnum.java +++ b/src/test/java/com/avaje/ebean/TestFilterWithEnum.java @@ -11,17 +11,17 @@ import com.avaje.tests.model.basic.ResetBasicData; public class TestFilterWithEnum extends BaseTestCase { @Test - public void test() { - + public void test() throws InterruptedException { + ResetBasicData.reset(); - + List allOrders = Ebean.find(Order.class).findList(); - + Filter filter = Ebean.filter(Order.class); List newOrders = filter.eq("status", Order.Status.NEW).filter(allOrders); - + Assert.assertNotNull(newOrders); - + } - + } diff --git a/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java b/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java index 4c6b775db..9a813451c 100644 --- a/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java +++ b/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java @@ -3,6 +3,7 @@ package com.avaje.ebean.bean; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.EBasic; import com.avaje.tests.model.basic.ResetBasicData; import org.junit.Test; @@ -11,6 +12,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -41,4 +43,37 @@ public class EntityBeanInterceptTest extends BaseTestCase { assertTrue(ebi.hasDirtyProperty(propertyNames)); } + + @Test + public void isPartial_when_new() { + + EBasic basic = new EBasic(); + EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept(); + assertThat(ebi.isPartial()).isTrue(); + } + + @Test + public void isPartial_when_partial() { + + EBasic basic = new EBasic(); + basic.setId(42); + basic.setName("some"); + EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept(); + assertThat(ebi.isPartial()).isTrue(); + } + + @Test + public void isPartial_when_full() { + + EBasic basic = new EBasic(); + basic.setId(42); + basic.setName("some"); + basic.setDescription("asd"); + basic.setSomeDate(null); + basic.setStatus(EBasic.Status.ACTIVE); + + EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept(); + assertThat(ebi.isPartial()).isFalse(); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java b/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java new file mode 100644 index 000000000..72ff5d1b7 --- /dev/null +++ b/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java @@ -0,0 +1,33 @@ +package com.avaje.ebean.config; + +import com.avaje.ebean.annotation.DocStoreEvent; +import org.junit.Test; + +import java.util.Properties; + +import static org.junit.Assert.*; + +public class DocStoreConfigTest { + + @Test + public void testLoadSettings() throws Exception { + + DocStoreConfig config = new DocStoreConfig(); + + Properties properties = new Properties(); + properties.setProperty("ebean.docstore.active", "true"); + properties.setProperty("ebean.docstore.bulkBatchSize", "99"); + properties.setProperty("ebean.docstore.url", "http://foo:9800"); + properties.setProperty("ebean.docstore.persist", "IGNORE"); + + PropertiesWrapper wrapper = new PropertiesWrapper("ebean", null, properties); + + config.loadSettings(wrapper); + + assertTrue(config.isActive()); + assertEquals("http://foo:9800", config.getUrl()); + assertEquals(DocStoreEvent.IGNORE, config.getPersist()); + assertEquals(99, config.getBulkBatchSize()); + + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java b/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java new file mode 100644 index 000000000..917d9653a --- /dev/null +++ b/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java @@ -0,0 +1,192 @@ +package com.avaje.ebean.plugin; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.OrderDetail; +import com.avaje.tests.model.basic.Person; +import com.avaje.tests.model.basic.Product; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class BeanTypeTest { + + static EbeanServer server = Ebean.getDefaultServer(); + + BeanType beanType(Class cls) { + return server.getPluginApi().getBeanType(cls); + } + + @Test + public void getBeanType() throws Exception { + assertThat(beanType(Order.class).getBeanType()).isEqualTo(Order.class); + } + + @Test + public void getTypeAtPath_when_ManyToOne() throws Exception { + BeanType orderType = beanType(Order.class); + BeanType customerType = orderType.getBeanTypeAtPath("customer"); + assertThat(customerType.getBeanType()).isEqualTo(Customer.class); + } + + @Test + public void getTypeAtPath_when_OneToMany() throws Exception { + BeanType orderType = beanType(Order.class); + BeanType detailsType = orderType.getBeanTypeAtPath("details"); + assertThat(detailsType.getBeanType()).isEqualTo(OrderDetail.class); + } + + @Test + public void getTypeAtPath_when_nested() throws Exception { + BeanType orderType = beanType(Order.class); + BeanType productType = orderType.getBeanTypeAtPath("details.product"); + assertThat(productType.getBeanType()).isEqualTo(Product.class); + } + + @Test(expected = RuntimeException.class) + public void getTypeAtPath_when_simpleType() throws Exception { + + beanType(Order.class).getBeanTypeAtPath("status"); + } + + @Test + public void createBean() throws Exception { + + assertThat(beanType(Order.class).createBean()).isNotNull(); + } + + @Test + public void property() throws Exception { + + Order order = new Order(); + order.setStatus(Order.Status.APPROVED); + Property statusProperty = beanType(Order.class).getProperty("status"); + + assertThat(statusProperty.getVal(order)).isEqualTo(order.getStatus()); + } + + @Test + public void getBaseTable() throws Exception { + + assertThat(beanType(Order.class).getBaseTable()).isEqualTo("o_order"); + } + + @Test + public void beanId_and_getBeanId() throws Exception { + + Order order = new Order(); + order.setId(42); + + Object id1 = beanType(Order.class).beanId(order); + Object id2 = beanType(Order.class).getBeanId(order); + + assertThat(id1).isEqualTo(order.getId()); + assertThat(id2).isEqualTo(order.getId()); + } + + @Test + public void setBeanId() throws Exception { + + Order order = new Order(); + beanType(Order.class).setBeanId(order, 42); + + assertThat(42).isEqualTo(order.getId()); + } + + @Test + public void isDocStoreIndex() throws Exception { + + assertThat(beanType(Order.class).isDocStoreMapped()).isFalse(); + assertThat(beanType(Person.class).isDocStoreMapped()).isFalse(); + + assertThat(beanType(Order.class).getDocMapping()).isNotNull(); + assertThat(beanType(Person.class).getDocMapping()).isNull(); + } + + @Test + public void docStore_getEmbedded() throws Exception { + + BeanDocType orderDocType = beanType(Order.class).docStore(); + FetchPath customer = orderDocType.getEmbedded("customer"); + assertThat(customer).isNotNull(); + assertThat(customer.getProperties(null)).contains("id","name"); + } + + @Test + public void docStore_getEmbeddedManyRoot() throws Exception { + + BeanDocType orderDocType = beanType(Order.class).docStore(); + + FetchPath detailsPath = orderDocType.getEmbedded("details"); + assertThat(detailsPath).isNotNull(); + + FetchPath detailsRoot = orderDocType.getEmbeddedManyRoot("details"); + assertThat(detailsRoot).isNotNull(); + assertThat(detailsRoot.getProperties(null)).containsExactly("id", "details"); + assertThat(detailsRoot.hasPath("details")).isTrue(); + } + + @Test + public void getDocStoreQueueId() throws Exception { + + assertThat(beanType(Order.class).getDocStoreQueueId()).isEqualTo("order"); + assertThat(beanType(Customer.class).getDocStoreQueueId()).isEqualTo("customer"); + } + + @Test + public void getDocStoreIndexType() throws Exception { + + assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order"); + assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer"); + } + + @Test + public void getDocStoreIndexName() throws Exception { + + assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order"); + assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer"); + } + + @Test + public void docStoreNested() throws Exception { + + FetchPath parse = PathProperties.parse("id,name"); + + FetchPath nestedCustomer = beanType(Order.class).docStore().getEmbedded("customer"); + assertThat(nestedCustomer.toString()).isEqualTo(parse.toString()); + } + + @Test + public void docStoreApplyPath() throws Exception { + + SpiQuery orderQuery = (SpiQuery)server.find(Order.class); + beanType(Order.class).docStore().applyPath(orderQuery); + + OrmQueryDetail detail = orderQuery.getDetail(); + assertThat(detail.getChunk("customer", false).getSelectProperties()) + .containsExactly("id", "name"); + } + + + @Test(expected = IllegalStateException.class) + public void docStoreIndex() throws Exception { + beanType(Order.class).docStore().index(1, new Order(), null); + } + + @Test(expected = IllegalStateException.class) + public void docStoreDeleteById() throws Exception { + beanType(Order.class).docStore().deleteById(1, null); + } + + @Test(expected = IllegalStateException.class) + public void docStoreUpdateEmbedded() throws Exception { + beanType(Order.class).docStore().updateEmbedded(1, "customer", "someJson", null); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java b/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java new file mode 100644 index 000000000..54ba6bd46 --- /dev/null +++ b/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java @@ -0,0 +1,79 @@ +package com.avaje.ebean.plugin; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class ExpressionPathTest { + + static EbeanServer server = Ebean.getDefaultServer(); + + BeanType beanType(Class cls) { + return server.getPluginApi().getBeanType(cls); + } + + @Test + public void containsMany_when_many() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("details").containsMany()).isTrue(); + } + + @Test + public void containsMany_when_manyChild() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("details.id").containsMany()).isTrue(); + } + + @Test + public void containsMany_when_manyGrandChild() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("details.product.sku").containsMany()).isTrue(); + } + + @Test + public void containsMany_when_one() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("customer.name").containsMany()).isFalse(); + } + + @Test + public void containsMany_when_oneWithMany() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("customer.contacts").containsMany()).isTrue(); + } + + @Test + public void containsMany_when_oneWithManyChild() throws Exception { + + BeanType beanType = beanType(Order.class); + assertThat(beanType.getExpressionPath("customer.contacts.firstName").containsMany()).isTrue(); + } + + @Test + public void set_when_basic() throws Exception { + + BeanType beanType = beanType(Order.class); + Order order = new Order(); + beanType.getExpressionPath("id").set(order, 42); + assertThat(order.getId()).isEqualTo(42); + } + + @Test + public void set_when_nested() throws Exception { + + BeanType beanType = beanType(Order.class); + Order order = new Order(); + beanType.getExpressionPath("customer.name").set(order, "Rob"); + assertThat(order.getCustomer().getName()).isEqualTo("Rob"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/plugin/PropertyTest.java b/src/test/java/com/avaje/ebean/plugin/PropertyTest.java new file mode 100644 index 000000000..80c49713d --- /dev/null +++ b/src/test/java/com/avaje/ebean/plugin/PropertyTest.java @@ -0,0 +1,56 @@ +package com.avaje.ebean.plugin; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +/** + */ +public class PropertyTest { + + static EbeanServer server = Ebean.getDefaultServer(); + + BeanType beanType(Class cls) { + return server.getPluginApi().getBeanType(cls); + } + + @Test + public void getVal() throws Exception { + + Customer customer = new Customer(); + + Order order = new Order(); + order.setCustomer(customer); + order.setStatus(Order.Status.APPROVED); + + Property statusProperty = beanType(Order.class).getProperty("status"); + assertThat(statusProperty.getVal(order)).isEqualTo(order.getStatus()); + + Property customerProperty = beanType(Order.class).getProperty("customer"); + assertThat(customerProperty.getVal(order)).isEqualTo(customer); + } + + @Test + public void isMany_when_not() { + + assertThat(beanType(Order.class).getProperty("status").isMany()).isFalse(); + assertThat(beanType(Order.class).getProperty("customer").isMany()).isFalse(); + } + + @Test + public void isMany_when_true() { + + assertThat(beanType(Order.class).getProperty("details").isMany()).isTrue(); + } + + @Test + public void name() { + assertThat(beanType(Order.class).getProperty("status").getName()).isEqualTo("status"); + assertThat(beanType(Order.class).getProperty("customer").getName()).isEqualTo("customer"); + assertThat(beanType(Order.class).getProperty("details").getName()).isEqualTo("details"); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java b/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java index 750d29e57..7f41b08e3 100644 --- a/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java +++ b/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java @@ -7,7 +7,12 @@ import org.junit.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; public class SpiServerTest { diff --git a/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java new file mode 100644 index 000000000..ff85cd96c --- /dev/null +++ b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java @@ -0,0 +1,88 @@ +package com.avaje.ebean.text.json; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.tests.model.basic.Customer; +import com.fasterxml.jackson.core.JsonParser; +import org.junit.Test; + +import java.io.StringReader; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class JsonBeanReaderTest extends BaseTestCase { + + static JsonContext json = Ebean.json(); + + @Test + public void read() throws Exception { + + JsonParser parser = getParser(); + JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null); + + Customer customer = beanReader.read(); + assertThat(customer.getId()).isEqualTo(42); + assertThat(customer.getName()).isEqualTo("dummy"); + } + + private JsonParser getParser() { + + Customer customer = new Customer(); + customer.setId(42); + customer.setName("dummy"); + + String rawJson = json.toJson(customer); + StringReader reader = new StringReader(rawJson); + + return json.createParser(reader); + } + + @Test + public void forJson() throws Exception { + + JsonParser parser = getParser(); + JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null); + beanReader.read(); + + JsonParser more = getParser(); + JsonBeanReader moreReader = beanReader.forJson(more); + + Customer customer = moreReader.read(); + assertThat(customer.getId()).isEqualTo(42); + assertThat(customer.getName()).isEqualTo("dummy"); + } + + @Test + public void persistenceContextPut_when_noPC() throws Exception { + + JsonParser parser = getParser(); + JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null); + beanReader.read(); + + Customer other = new Customer(); + other.setId(54); + + beanReader.persistenceContextPut(54, other); + } + + @Test + public void persistenceContextPut_when_hasPC() throws Exception { + + JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true); + + JsonParser parser = getParser(); + JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, options); + Customer customer = beanReader.read(); + + Customer other = new Customer(); + other.setId(54); + + beanReader.persistenceContextPut(54, other); + PersistenceContext pc = beanReader.getPersistenceContext(); + assertThat(pc.get(Customer.class, 54)).isSameAs(other); + assertThat(pc.get(Customer.class, 42)).isSameAs(customer); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java index 70646ab5f..affad9527 100644 --- a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java +++ b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java @@ -3,15 +3,24 @@ package com.avaje.ebean.text.json; import com.avaje.ebean.Ebean; import com.avaje.ebean.EbeanServer; import com.avaje.ebean.text.PathProperties; +import com.avaje.tests.model.basic.Contact; import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.ResetBasicData; import com.fasterxml.jackson.core.JsonGenerator; import org.junit.Test; import java.io.StringReader; import java.io.StringWriter; +import java.util.List; import java.util.Map; -import static org.junit.Assert.*; +import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; public class JsonContextTest { @@ -25,6 +34,56 @@ public class JsonContextTest { assertFalse(json.isSupportedType(System.class)); } + @Test + public void test_jsonWithPersistenceContext() { + + ResetBasicData.reset(); + + List orders = Ebean.find(Order.class) + .fetch("customer", "id, name") + .where().eq("customer.id", 1) + .findList(); + + String json = Ebean.json().toJson(orders); + + List orders1 = Ebean.json().toList(Order.class, json); + + Customer customer = null; + for (Order order : orders1) { + Customer tempCustomer = order.getCustomer(); + if (customer == null) { + customer = tempCustomer; + } else { + assertThat(tempCustomer).isSameAs(customer); + } + } + } + + @Test + public void test_json_loadContext() { + + ResetBasicData.reset(); + + List orders = Ebean.find(Order.class) + .select("status") + .fetch("customer", "id, name") + .findList(); + + String json = Ebean.json().toJson(orders); + + JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true); + + List orders1 = Ebean.json().toList(Order.class, json, options); + + for (Order order : orders1) { + Customer customer = order.getCustomer(); + customer.getName(); + customer.getSmallnote(); + List contacts = customer.getContacts(); + contacts.size(); + } + } + @Test public void test_toObject() throws Exception { @@ -91,7 +150,7 @@ public class JsonContextTest { assertSame(customer, custReadVisitor.bean); assertEquals("foo", custReadVisitor.unmapped.get("unknownProp")); assertEquals(2, custReadVisitor.unmapped.size()); - assertEquals("foobie", ((Map)custReadVisitor.unmapped.get("extraProp")).get("name")); + assertEquals("foobie", ((Map) custReadVisitor.unmapped.get("extraProp")).get("name")); assertEquals("bo", ((Map) custReadVisitor.unmapped.get("extraProp")).get("sim")); } diff --git a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java index c05498ae6..7ef2f9775 100644 --- a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java +++ b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java @@ -1,7 +1,6 @@ package com.avaje.ebean.text.json; import com.avaje.ebean.FetchPath; -import org.junit.Assert; import org.junit.Test; import java.util.Set; diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 6ed966cff..b6236ffcf 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -8,17 +8,14 @@ import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.dbmigration.DdlGenerator; import com.avaje.ebean.event.readaudit.ReadAuditLogger; import com.avaje.ebean.event.readaudit.ReadAuditPrepare; import com.avaje.ebean.meta.MetaInfoManager; import com.avaje.ebean.plugin.SpiServer; import com.avaje.ebean.text.csv.CsvReader; import com.avaje.ebean.text.json.JsonContext; -import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.query.CQuery; -import com.avaje.ebeaninternal.server.query.CQueryEngine; import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent; import javax.persistence.OptimisticLockException; @@ -82,6 +79,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public DocumentStore docStore() { + return null; + } + @Override public ReadAuditLogger getReadAuditLogger() { return null; @@ -97,6 +99,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } + @Override + public BeanDescriptor getBeanDescriptorByQueueId(String queueId) { + return null; + } + @Override public List> getBeanDescriptors() { return null; diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java index bb9cde51c..29cd54f06 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java @@ -9,10 +9,11 @@ import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.tests.model.basic.EBasic; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; -/** - */ public class BeanDescriptorTest { @Test diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java new file mode 100644 index 000000000..b33d422cb --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java @@ -0,0 +1,85 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyAdapter; +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping; +import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BeanDescriptor_documentMappingTest extends BaseTestCase { + + + @Test + public void docMapping() { + + BeanDescriptor desc = getBeanDescriptor(Order.class); + + DocumentMapping documentMapping = desc.getDocMapping(); + + DocPropertyMapping properties = documentMapping.getProperties(); + + assertThat(properties).isNotNull(); + } + + @Test + public void docMapping_visitor() { + + BeanDescriptor desc = getBeanDescriptor(Order.class); + + DocumentMapping documentMapping = desc.getDocMapping(); + + DocPropertyMapping properties = documentMapping.getProperties(); + + assertThat(properties).isNotNull(); + + TDVisitor tdVisitor = new TDVisitor(); + documentMapping.visit(tdVisitor); + + assertThat(tdVisitor.sb.toString()).isEqualTo("{status,orderDate,shipDate, object{customer:id,name,}customerName, nested{details: [id,orderQty,shipQty,unitPrice,cretime,updtime,]}cretime,updtime,}"); + } + + class TDVisitor extends DocPropertyAdapter { + + StringBuilder sb = new StringBuilder(); + + @Override + public void visitProperty(DocPropertyMapping property) { + sb.append(property.getName()+","); + } + + @Override + public void visitBegin() { + + sb.append("{"); + } + + @Override + public void visitEnd() { + sb.append("}"); + } + + @Override + public void visitBeginObject(DocPropertyMapping property) { + sb.append(" object{"+property.getName()+":"); + } + + @Override + public void visitEndObject(DocPropertyMapping property) { + sb.append("}"); + } + + @Override + public void visitBeginList(DocPropertyMapping property) { + sb.append(" nested{"+property.getName()+": ["); + } + + @Override + public void visitEndList(DocPropertyMapping property) { + sb.append("]}"); + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java new file mode 100644 index 000000000..c56927a3c --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java @@ -0,0 +1,23 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.plugin.Property; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BeanDescriptor_propertiesTest extends BaseTestCase { + + @Test + public void allProperties() { + + BeanDescriptor desc = getBeanDescriptor(Order.class); + Collection props = desc.allProperties(); + + assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java index 01ba7417b..f9c3e9195 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java @@ -21,15 +21,15 @@ public class BeanDescriptor_whenCreatedPropertyTest extends BaseTestCase { BeanDescriptor desc = server.getBeanDescriptor(Customer.class); - BeanProperty whenCreatedProperty = desc.findWhenCreatedProperty(); + BeanProperty whenCreatedProperty = desc.getWhenCreatedProperty(); assertEquals("cretime",whenCreatedProperty.getDbColumn()); - BeanProperty whenModifiedProperty = desc.findWhenModifiedProperty(); + BeanProperty whenModifiedProperty = desc.getWhenModifiedProperty(); assertEquals("updtime",whenModifiedProperty.getDbColumn()); BeanDescriptor eBasicDesc = server.getBeanDescriptor(EBasic.class); - assertNull(eBasicDesc.findWhenCreatedProperty()); - assertNull(eBasicDesc.findWhenModifiedProperty()); + assertNull(eBasicDesc.getWhenCreatedProperty()); + assertNull(eBasicDesc.getWhenModifiedProperty()); } } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java new file mode 100644 index 000000000..9997aba3e --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java @@ -0,0 +1,24 @@ +package com.avaje.ebeaninternal.server.expression; + + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Order; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.IOException; +import java.io.StringWriter; + +public abstract class BaseElasticTest extends BaseTestCase { + + public static JsonFactory factory = new JsonFactory(); + + public ElasticExpressionContext context(StringWriter sb) throws IOException { + + BeanDescriptor desc = getBeanDescriptor(Order.class); + JsonGenerator gen = factory.createGenerator(sb); + return new ElasticExpressionContext(gen, desc); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java index 3c0bc757a..0c8a3b989 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java @@ -54,7 +54,7 @@ public class DefaultExampleExpressionTest extends BaseTestCase { DefaultExampleExpression prepare(DefaultExampleExpression expr) { SpiQuery query = (SpiQuery)spiEbeanServer().find(Customer.class); - BeanQueryRequest request = create(query, customerBeanDescriptor()); + BeanQueryRequest request = create(query); expr.prepareExpression(request); return expr; @@ -92,8 +92,8 @@ public class DefaultExampleExpressionTest extends BaseTestCase { } - private OrmQueryRequest create(SpiQuery query, BeanDescriptor desc) { - return new OrmQueryRequest(null, null, query, desc, null); + private OrmQueryRequest create(SpiQuery query) { + return new OrmQueryRequest(null, null, query, null); } @Test diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java similarity index 90% rename from src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java rename to src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java index b3ff17e81..78752ba3d 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java @@ -8,12 +8,12 @@ import java.util.Arrays; import static org.assertj.core.api.StrictAssertions.assertThat; -public class ExistsExpressionTest { +public class ExistsQueryExpressionTest { @NotNull - private ExistsExpression exp(boolean not, String sql, Object... bindValues) { - return new ExistsExpression(not, sql, Arrays.asList(bindValues)); + private ExistsQueryExpression exp(boolean not, String sql, Object... bindValues) { + return new ExistsQueryExpression(not, sql, Arrays.asList(bindValues)); } @Test diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java new file mode 100644 index 000000000..2ad96cc4d --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.server.expression; + + +import org.junit.Test; + +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SimpleExpressionElasticTest extends BaseElasticTest { + + + @Test + public void writeElastic() throws Exception { + + SimpleExpression eqExp = new SimpleExpression("name", Op.EQ, "rob"); + + StringWriter sb = new StringWriter(); + ElasticExpressionContext context = context(sb); + eqExp.writeElastic(context); + context.json().flush(); + + String json = sb.toString(); + + assertThat(json).isEqualTo("{\"term\":{\"name\":\"rob\"}}"); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java new file mode 100644 index 000000000..720a4225a --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java @@ -0,0 +1,88 @@ +package com.avaje.ebeaninternal.server.querydefn; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.expression.BaseElasticTest; +import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext; +import com.avaje.tests.model.basic.Order; +import com.fasterxml.jackson.core.JsonGenerator; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultOrmQueryElasticTest extends BaseElasticTest { + + @Test + public void writeElastic_on_SpiExpressionList() throws IOException { + + Query query = Ebean.find(Order.class) + .where().eq("customer.name", "Rob") + .query(); + + SpiQuery spiQuery = (SpiQuery)query; + + SpiExpressionList whereExpressions = spiQuery.getWhereExpressions(); + + StringWriter sb = new StringWriter(); + ElasticExpressionContext context = context(sb); + JsonGenerator json = context.json(); + json.writeStartObject(); + json.writeFieldName("filter"); + + whereExpressions.writeElastic(context); + + json.writeEndObject(); + context.flush(); + + assertThat(sb.toString()).isEqualTo("{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}"); + } + + @Test + public void writeElastic() throws IOException { + + Query query = Ebean.find(Order.class) + .select("status, customer.name, details.product.id") + .where().eq("customer.name", "Rob") + .query(); + + SpiQuery spiQuery = (SpiQuery)query; + + StringWriter sb = new StringWriter(); + ElasticExpressionContext context = context(sb); + + spiQuery.writeElastic(context); + context.flush(); + + assertThat(sb.toString()).isEqualTo("{\"fields\":[\"status\",\"customer.name\",\"details.product.id\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}"); + } + + @Test + public void asElasticQuery() throws IOException { + + String elasticQuery = Ebean.find(Order.class) + .select("status") + .where().eq("customer.name", "Rob") + .query().asElasticQuery(); + + + assertThat(elasticQuery).isEqualTo("{\"fields\":[\"status\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}"); + } + + @Test + public void asElasticQuery_firstRowsMaxRows() throws IOException { + + String elasticQuery = Ebean.find(Order.class) + .select("status") + .setFirstRow(3) + .setMaxRows(100) + .where().eq("customer.name", "Rob") + .query().asElasticQuery(); + + assertThat(elasticQuery).isEqualTo("{\"from\":3,\"size\":100,\"fields\":[\"status\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}"); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java index 36f9abb03..239ef2698 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java @@ -2,7 +2,8 @@ package com.avaje.ebeaninternal.server.querydefn; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; -import com.avaje.ebean.EbeanServer; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory; import com.avaje.tests.model.basic.Order; import org.junit.Assert; @@ -81,11 +82,13 @@ public class TestQueryLanguage extends BaseTestCase { private DefaultOrmQuery check(String q) { - EbeanServer server = Ebean.getServer(null); + SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null); OrmQueryDetailParser p = new OrmQueryDetailParser(q); p.parse(); - DefaultOrmQuery qry = new DefaultOrmQuery(Order.class, server, + + BeanDescriptor desc = server.getBeanDescriptor(Order.class); + DefaultOrmQuery qry = new DefaultOrmQuery(desc, server, new DefaultExpressionFactory(false), (String) null); p.assign(qry); diff --git a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java new file mode 100644 index 000000000..4b3f64772 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java @@ -0,0 +1,55 @@ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.ResetBasicData; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.sql.Date; +import java.util.List; + +import static org.junit.Assert.assertTrue; + +public class WriteJsonDirtyTest { + + @Test + public void test() throws IOException { + + ResetBasicData.reset(); + List customers = Ebean.find(Customer.class).findList(); + + Customer customer = Ebean.find(Customer.class).setId(customers.get(0).getId()) + .setUseCache(false) + .findUnique(); + + SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null); + BeanDescriptor descriptor = server.getBeanDescriptor(Customer.class); + + customer.setName("dirtyCustName"); + customer.setAnniversary(new Date(System.currentTimeMillis())); + + EntityBean entityBean = (EntityBean)customer; + boolean[] dirtyProperties = entityBean._ebean_getIntercept().getDirtyProperties(); + + StringWriter writer = new StringWriter(); + JsonFactory jsonFactory = new JsonFactory(); + JsonGenerator generator = jsonFactory.createGenerator(writer); + + WriteJson writeJson = new WriteJson(server, generator, null, null, null, null); + descriptor.jsonWriteDirty(writeJson, entityBean, dirtyProperties); + + generator.flush(); + generator.close(); + + String jsonContent = writer.toString(); + assertTrue(jsonContent.contains("\"name\":")); + assertTrue(jsonContent.contains("\"anniversary\":")); + } +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java index bc5219455..a200d80df 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.text.json; import com.avaje.ebean.config.JsonConfig; import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.FetchPath; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import org.junit.Test; @@ -19,8 +20,8 @@ public class WriteJsonTest { JsonFactory jsonFactory = new JsonFactory(); JsonGenerator generator = jsonFactory.createGenerator(new StringWriter()); - PathProperties pathProperties = PathProperties.parse("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))"); - WriteJson writeJson = new WriteJson(null, generator, pathProperties, null, null, JsonConfig.Include.ALL); + FetchPath fetchPath = PathProperties.parse("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))"); + WriteJson writeJson = new WriteJson(null, generator, fetchPath, null, null, JsonConfig.Include.ALL); WriteJson.WriteBean rootLevel = writeJson.createWriteBean(null, null); assertTrue(rootLevel.currentIncludeProps.contains("id")); diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java b/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java index 2c3878fc1..75e86eca3 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java +++ b/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java @@ -18,9 +18,9 @@ public class TestEnumToBeanType { OrdinalEnum ordinalEnum = new ScalarTypeEnumStandard.OrdinalEnum(Order.Status.class); EnumToDbValueMap beanDbMap = EnumToDbValueMap.create(false); - beanDbMap.add(Customer.Status.ACTIVE, "A"); - beanDbMap.add(Customer.Status.NEW, "N"); - beanDbMap.add(Customer.Status.INACTIVE, "I"); + beanDbMap.add(Customer.Status.ACTIVE, "A", Customer.Status.ACTIVE.name()); + beanDbMap.add(Customer.Status.NEW, "N", Customer.Status.NEW.name()); + beanDbMap.add(Customer.Status.INACTIVE, "I", Customer.Status.INACTIVE.name()); ScalarTypeEnumWithMapping withMapping = new ScalarTypeEnumWithMapping(beanDbMap, Customer.Status.class, 1); diff --git a/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java new file mode 100644 index 000000000..6c7a61d99 --- /dev/null +++ b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java @@ -0,0 +1,68 @@ +package com.avaje.ebeanservice.docstore.api.support; + + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DocStoreBeanBaseAdapterTest extends BaseTestCase { + + + @Test + @SuppressWarnings("unchecked") + public void test_basic_construction() throws Exception { + + SpiEbeanServer server = spiEbeanServer(); + BeanDescriptor orderDesc = server.getBeanDescriptor(Order.class); + + DeployBeanDescriptor deployDesc = (DeployBeanDescriptor)mock(DeployBeanDescriptor.class); + + TDAdapter adapter = new TDAdapter(orderDesc, deployDesc); + + assertThat(adapter.getIndexName()).isEqualTo("order"); + assertThat(adapter.getIndexType()).isEqualTo("order"); + assertThat(adapter.getQueueId()).isEqualTo("order"); + } + + static class TDAdapter extends DocStoreBeanBaseAdapter { + + TDAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) { + super(desc, deploy); + } + + @Override + public void applyPath(Query query) { + } + + @Override + public void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException { + } + + @Override + public void index(Object idValue, Object entityBean, DocStoreUpdateContext txn) throws IOException { + } + + @Override + public void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException { + } + + @Override + public void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException { + } + + @Override + public void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException { + } + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEventTest.java b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEventTest.java new file mode 100644 index 000000000..f864180a5 --- /dev/null +++ b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEventTest.java @@ -0,0 +1,65 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.DocStoreQueueEntry; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.plugin.BeanDocType; +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; +import com.avaje.tests.model.basic.Order; +import org.assertj.core.api.StrictAssertions; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DocStoreDeleteEventTest { + + static EbeanServer server = Ebean.getDefaultServer(); + + BeanType beanType(Class cls) { + return server.getPluginApi().getBeanType(cls); + } + + BeanType orderType() { + return beanType(Order.class); + } + + @Test + @SuppressWarnings("unchecked") + public void docStoreUpdate() throws Exception { + + BeanType mock = (BeanType) Mockito.mock(BeanType.class); + BeanDocType mockDocType = (BeanDocType)Mockito.mock(BeanDocType.class); + when(mock.docStore()).thenReturn(mockDocType); + + DocStoreDeleteEvent event = new DocStoreDeleteEvent(mock, 42); + event.docStoreUpdate(null); + + verify(mock, times(1)).docStore(); + verify(mockDocType, times(1)).deleteById(42, null); + } + + @Test + public void addToQueue() throws Exception { + + DocStoreDeleteEvent event = new DocStoreDeleteEvent(orderType(), 42); + + DocStoreUpdates updates = new DocStoreUpdates(); + event.addToQueue(updates); + + List queueEntries = updates.getQueueEntries(); + assertThat(queueEntries).hasSize(1); + + DocStoreQueueEntry entry = queueEntries.get(0); + StrictAssertions.assertThat(entry.getBeanId()).isEqualTo(42); + StrictAssertions.assertThat(entry.getQueueId()).isEqualTo("order"); + StrictAssertions.assertThat(entry.getPath()).isNull(); + assertThat(entry.getType()).isEqualTo(DocStoreQueueEntry.Action.DELETE); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEventTest.java b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEventTest.java new file mode 100644 index 000000000..915556868 --- /dev/null +++ b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEventTest.java @@ -0,0 +1,68 @@ +package com.avaje.ebeanservice.docstore.api.support; + +import com.avaje.ebean.DocStoreQueueEntry; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.plugin.BeanDocType; +import com.avaje.ebean.plugin.BeanType; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; +import com.avaje.tests.model.basic.Order; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DocStoreIndexEventTest { + + static EbeanServer server = Ebean.getDefaultServer(); + + BeanType beanType(Class cls) { + return server.getPluginApi().getBeanType(cls); + } + + BeanType orderType() { + return beanType(Order.class); + } + + @Test + @SuppressWarnings("unchecked") + public void docStoreUpdate() throws Exception { + + BeanType mock = (BeanType)Mockito.mock(BeanType.class); + BeanDocType mockDocType = (BeanDocType)Mockito.mock(BeanDocType.class); + when(mock.docStore()).thenReturn(mockDocType); + + Order bean = new Order(); + DocStoreIndexEvent event = new DocStoreIndexEvent(mock, 42, bean); + + event.docStoreUpdate(null); + + verify(mock, times(1)).docStore(); + verify(mockDocType, times(1)).index(42, bean, null); + } + + @Test + public void addToQueue() throws Exception { + + Order bean = new Order(); + + DocStoreIndexEvent event = new DocStoreIndexEvent(orderType(), 42, bean); + + DocStoreUpdates updates = new DocStoreUpdates(); + event.addToQueue(updates); + + List queueEntries = updates.getQueueEntries(); + assertThat(queueEntries).hasSize(1); + + DocStoreQueueEntry entry = queueEntries.get(0); + assertThat(entry.getBeanId()).isEqualTo(42); + assertThat(entry.getQueueId()).isEqualTo("order"); + assertThat(entry.getPath()).isNull(); + assertThat(entry.getType()).isEqualTo(DocStoreQueueEntry.Action.INDEX); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/batchload/TestLoadOnDirty.java b/src/test/java/com/avaje/tests/batchload/TestLoadOnDirty.java index 9d4a95dc2..a18014f2b 100644 --- a/src/test/java/com/avaje/tests/batchload/TestLoadOnDirty.java +++ b/src/test/java/com/avaje/tests/batchload/TestLoadOnDirty.java @@ -2,6 +2,8 @@ package com.avaje.tests.batchload; import java.util.List; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; import org.junit.Assert; import org.junit.Test; @@ -35,6 +37,15 @@ public class TestLoadOnDirty extends BaseTestCase { Assert.assertTrue(beanState.getChangedProps().contains("name")); Assert.assertEquals(1, beanState.getChangedProps().size()); + EntityBeanIntercept ebi = ((EntityBean) customer)._ebean_getIntercept(); + boolean[] dirtyProperties = ebi.getDirtyProperties(); + for (int i = 0; i < dirtyProperties.length; i++) { + if (dirtyProperties[i]) { + String dirtyPropertyName = ebi.getProperty(i); + Assert.assertEquals("name", dirtyPropertyName); + } + } + customer.setStatus(Customer.Status.INACTIVE); Assert.assertTrue(beanState.isDirty()); diff --git a/src/test/java/com/avaje/tests/model/basic/Contact.java b/src/test/java/com/avaje/tests/model/basic/Contact.java index 67f7cccf5..fe4907438 100644 --- a/src/test/java/com/avaje/tests/model/basic/Contact.java +++ b/src/test/java/com/avaje/tests/model/basic/Contact.java @@ -12,8 +12,11 @@ import javax.persistence.Version; import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.annotation.ChangeLog; import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.DocStoreEmbedded; +import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.annotation.Index; +@DocStore @Index(columnNames = {"last_name","first_name"}) @ChangeLog @Entity @@ -32,6 +35,7 @@ public class Contact { String mobile; String email; + @DocStoreEmbedded(doc="id,name") @ManyToOne(optional=false) Customer customer; diff --git a/src/test/java/com/avaje/tests/model/basic/Country.java b/src/test/java/com/avaje/tests/model/basic/Country.java index ad9fdb6a6..388dba57a 100644 --- a/src/test/java/com/avaje/tests/model/basic/Country.java +++ b/src/test/java/com/avaje/tests/model/basic/Country.java @@ -4,6 +4,7 @@ import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.annotation.CacheTuning; import com.avaje.ebean.annotation.ChangeLog; import com.avaje.ebean.annotation.ChangeLogInsertMode; +import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.annotation.ReadAudit; import javax.persistence.Entity; @@ -14,6 +15,7 @@ import javax.validation.constraints.Size; /** * Country entity bean. */ +@DocStore @ReadAudit @ChangeLog(inserts = ChangeLogInsertMode.INCLUDE) @CacheStrategy(readOnly = true, warmingQuery = "order by name") diff --git a/src/test/java/com/avaje/tests/model/basic/Customer.java b/src/test/java/com/avaje/tests/model/basic/Customer.java index dc6eb4f79..738f3e89a 100644 --- a/src/test/java/com/avaje/tests/model/basic/Customer.java +++ b/src/test/java/com/avaje/tests/model/basic/Customer.java @@ -4,6 +4,8 @@ import com.avaje.ebean.annotation.ChangeLog; import com.avaje.ebean.annotation.ChangeLogInsertMode; import com.avaje.ebean.annotation.DbComment; import com.avaje.ebean.annotation.DbEnumValue; +import com.avaje.ebean.annotation.DocStoreEmbedded; +import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.annotation.JsonIgnore; import com.avaje.ebean.annotation.Where; import com.avaje.tests.model.basic.finder.CustomerFinder; @@ -24,6 +26,7 @@ import java.util.concurrent.locks.ReentrantLock; /** * Customer entity bean. */ +@DocStore @ChangeLog(inserts = ChangeLogInsertMode.EXCLUDE, updatesThatInclude = {"name","status"}) @Entity @Table(name = "o_customer") @@ -77,9 +80,11 @@ public class Customer extends BasicDomain { @NotNull(groups = { ValidationGroupSomething.class }) Date anniversary; + @DocStoreEmbedded(doc="*,country(*)") @ManyToOne(cascade = CascadeType.ALL) Address billingAddress; + @DocStoreEmbedded(doc="*,country(*)") @ManyToOne(cascade = CascadeType.ALL) Address shippingAddress; diff --git a/src/test/java/com/avaje/tests/model/basic/Order.java b/src/test/java/com/avaje/tests/model/basic/Order.java index 56ed489a3..b316a96d0 100644 --- a/src/test/java/com/avaje/tests/model/basic/Order.java +++ b/src/test/java/com/avaje/tests/model/basic/Order.java @@ -1,10 +1,11 @@ package com.avaje.tests.model.basic; -import java.io.Serializable; -import java.sql.Date; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; +import com.avaje.ebean.annotation.ChangeLog; +import com.avaje.ebean.annotation.DocStore; +import com.avaje.ebean.annotation.DocStoreEmbedded; +import com.avaje.ebean.annotation.Formula; +import com.avaje.ebean.annotation.WhenCreated; +import com.avaje.ebean.annotation.Where; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -20,16 +21,16 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.Version; import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlType; - -import com.avaje.ebean.annotation.ChangeLog; -import com.avaje.ebean.annotation.Formula; -import com.avaje.ebean.annotation.WhenCreated; -import com.avaje.ebean.annotation.Where; +import java.io.Serializable; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; /** * Order entity bean. */ +@DocStore @ChangeLog @Entity @Table(name = "o_order") @@ -37,7 +38,6 @@ public class Order implements Serializable { private static final long serialVersionUID = 1L; - @XmlType(name = "status") public enum Status { NEW, APPROVED, @@ -86,9 +86,9 @@ public class Order implements Serializable { @NotNull @ManyToOne @JoinColumn(name = "kcustomer_id") + @DocStoreEmbedded(doc = "id,name") Customer customer; - //@Basic(fetch=FetchType.LAZY) @Column(name = "name", table = "o_customer") String customerName; @@ -101,6 +101,7 @@ public class Order implements Serializable { @Where(clause = "${ta}.id > 0") @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") @OrderBy("id asc, orderQty asc, cretime desc") + @DocStoreEmbedded List details; @OneToMany(cascade = CascadeType.ALL, mappedBy = "order") diff --git a/src/test/java/com/avaje/tests/model/basic/OrderDetail.java b/src/test/java/com/avaje/tests/model/basic/OrderDetail.java index 69ed1af69..962a884a0 100644 --- a/src/test/java/com/avaje/tests/model/basic/OrderDetail.java +++ b/src/test/java/com/avaje/tests/model/basic/OrderDetail.java @@ -1,5 +1,7 @@ package com.avaje.tests.model.basic; +import com.avaje.ebean.annotation.DocStoreEmbedded; + import java.io.Serializable; import java.sql.Timestamp; @@ -31,6 +33,7 @@ public class OrderDetail implements Serializable { Double unitPrice; @ManyToOne + @DocStoreEmbedded(doc = "id,name,sku") Product product; Timestamp cretime; diff --git a/src/test/java/com/avaje/tests/model/basic/Product.java b/src/test/java/com/avaje/tests/model/basic/Product.java index df487f101..711956ab3 100644 --- a/src/test/java/com/avaje/tests/model/basic/Product.java +++ b/src/test/java/com/avaje/tests/model/basic/Product.java @@ -11,99 +11,101 @@ import javax.validation.constraints.Size; import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.annotation.CreatedTimestamp; +import com.avaje.ebean.annotation.DocStore; /** * Product entity bean. */ -@CacheStrategy(warmingQuery="order by name") +@DocStore +@CacheStrategy(warmingQuery = "order by name") @Entity -@Table(name="o_product") +@Table(name = "o_product") public class Product implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Id - Integer id; + @Id + Integer id; - @Size(max=20) - String sku; + @Size(max = 20) + String sku; - String name; + String name; - @CreatedTimestamp - Timestamp cretime; + @CreatedTimestamp + Timestamp cretime; - @Version - Timestamp updtime; + @Version + Timestamp updtime; - /** - * Return id. - */ - public Integer getId() { - return id; - } + /** + * Return id. + */ + public Integer getId() { + return id; + } - /** - * Set id. - */ - public void setId(Integer id) { - this.id = id; - } + /** + * Set id. + */ + public void setId(Integer id) { + this.id = id; + } - /** - * Return sku. - */ - public String getSku() { - return sku; - } + /** + * Return sku. + */ + public String getSku() { + return sku; + } - /** - * Set sku. - */ - public void setSku(String sku) { - this.sku = sku; - } + /** + * Set sku. + */ + public void setSku(String sku) { + this.sku = sku; + } - /** - * Return name. - */ - public String getName() { - return name; - } + /** + * Return name. + */ + public String getName() { + return name; + } - /** - * Set name. - */ - public void setName(String name) { - this.name = name; - } + /** + * Set name. + */ + public void setName(String name) { + this.name = name; + } - /** - * Return cretime. - */ - public Timestamp getCretime() { - return cretime; - } + /** + * Return cretime. + */ + public Timestamp getCretime() { + return cretime; + } - /** - * Set cretime. - */ - public void setCretime(Timestamp cretime) { - this.cretime = cretime; - } + /** + * Set cretime. + */ + public void setCretime(Timestamp cretime) { + this.cretime = cretime; + } - /** - * Return updtime. - */ - public Timestamp getUpdtime() { - return updtime; - } + /** + * Return updtime. + */ + public Timestamp getUpdtime() { + return updtime; + } + + /** + * Set updtime. + */ + public void setUpdtime(Timestamp updtime) { + this.updtime = updtime; + } - /** - * Set updtime. - */ - public void setUpdtime(Timestamp updtime) { - this.updtime = updtime; - } - } diff --git a/src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java b/src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java index 51df03423..ae2e2bc3f 100644 --- a/src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java +++ b/src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java @@ -52,7 +52,7 @@ public class TestOrderByWithMany extends BaseTestCase { String lazyLoadSql = loggedSql.get(1); // contains the foreign key back to the parent bean (t0.order_id) - Assert.assertTrue(lazyLoadSql.contains("select t0.order_id c0, t0.id")); + Assert.assertTrue(lazyLoadSql, lazyLoadSql.contains("select t0.order_id c0, t0.id")); Assert.assertTrue(lazyLoadSql.contains("order by t0.order_id, t0.id, t0.order_qty, t0.cretime desc")); } diff --git a/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java b/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java index 2ecce1ae3..14e636075 100644 --- a/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java +++ b/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java @@ -52,7 +52,7 @@ public class TestManyLazyLoadingQuery extends BaseTestCase { query0.setLazyLoadForParents(beanProperty); - beanProperty.addWhereParentIdIn(query0, parentIds); + beanProperty.addWhereParentIdIn(query0, parentIds, false); query0.findList(); diff --git a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java index f936d1578..2db845ca0 100644 --- a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java +++ b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java @@ -29,7 +29,7 @@ public class TestJsonBeanDescriptorParse extends BaseTestCase { StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello rob\"}"); JsonParser parser = server.json().createParser(reader); - ReadJson readJson = new ReadJson(parser, null, null); + ReadJson readJson = new ReadJson(descriptor, parser, null, null); Customer customer = descriptor.jsonRead(readJson, null); diff --git a/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java b/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java index 51776e643..8e83930aa 100644 --- a/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java +++ b/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java @@ -11,6 +11,7 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -100,9 +101,9 @@ public class TestUpdateAllLoadedProperties extends BaseTestCase { List loggedSql = LoggedSqlCollector.stop(); - assertEquals(2, loggedSql.size()); - assertTrue(loggedSql.get(0), loggedSql.get(0).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind(")); - assertTrue(loggedSql.get(1), loggedSql.get(1).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind(")); + assertThat(loggedSql).hasSize(2); + assertThat(loggedSql.get(0)).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind("); + assertThat(loggedSql.get(1)).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind("); } } diff --git a/src/test/resources/junk.json b/src/test/resources/junk.json index 02880f9ba..3f6199367 100644 --- a/src/test/resources/junk.json +++ b/src/test/resources/junk.json @@ -1,20 +1,22 @@ { - "txnId": "ABCD", - "txnState": "C", - "txnBatch": 0, - "txnPosition": 0, - "userId": "user234", - "userIpAddress": "123.4.5.6", - "object": "mytable", - "objectId": "11", - "change": "I", - "eventTime": 1440465178268, - "values": { - "name": { - "new": "rob" - }, - "modified": { - "new": 1440465178267 + "fields": [ + "status", + "customer.name", + "details.product.id" + ], + "query": { + "filtered": { + "filter": { + "bool": { + "must": [ + { + "match": { + "customer.name": "Rob" + } + } + ] + } + } } } -} +} \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index e8064551d..11847003c 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -72,14 +72,16 @@ - + - - - + + + + +